Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
485 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/AbstractProcess.cpp b/src/AbstractProcess.cpp
index 5cdf0d2..1bd9e7f 100644
--- a/src/AbstractProcess.cpp
+++ b/src/AbstractProcess.cpp
@@ -1,6 +1,17 @@
#include "AbstractProcess.h"
void AbstractPtyProcess::setChangeDir(QString const &dir)
{
change_dir = dir;
}
+
+void AbstractPtyProcess::setVariant(QVariant const &value)
+{ // setUserDataという名前だとQObject::setUserDataとかぶる
+ user_data = value;
+}
+
+QVariant const &AbstractPtyProcess::userVariant() const
+{
+ return user_data;
+}
+
diff --git a/src/AbstractProcess.h b/src/AbstractProcess.h
index 6cd8877..ac1d3b1 100644
--- a/src/AbstractProcess.h
+++ b/src/AbstractProcess.h
@@ -1,27 +1,31 @@
#ifndef ABSTRACTPROCESS_H
#define ABSTRACTPROCESS_H
#include <QObject>
+#include <QVariant>
#include <functional>
class QString;
class AbstractPtyProcess : public QObject {
Q_OBJECT
protected:
QString change_dir;
+ QVariant user_data;
public:
void setChangeDir(QString const &dir);
+ void setVariant(QVariant const &userdata);
+ QVariant const &userVariant() const;
virtual bool isRunning() const = 0;
virtual void writeInput(char const *ptr, int len) = 0;
virtual int readOutput(char *ptr, int len) = 0;
- virtual void start(QString const &cmd) = 0;
+ virtual void start(QString const &cmd, QVariant const &userdata = QVariant()) = 0;
virtual bool wait(unsigned long time = ULONG_MAX) = 0;
virtual void stop() = 0;
virtual int getExitCode() const = 0;
virtual QString getMessage() const = 0;
signals:
- void completed();
+ void completed(QVariant);
};
#endif // ABSTRACTPROCESS_H
diff --git a/src/AvatarLoader.h b/src/AvatarLoader.h
index da372b7..4f8ba2e 100644
--- a/src/AvatarLoader.h
+++ b/src/AvatarLoader.h
@@ -1,37 +1,37 @@
#ifndef AVATARLOADER_H
#define AVATARLOADER_H
#include "GitHubAPI.h"
#include <QIcon>
#include <QThread>
#include <QMutex>
#include <deque>
#include <QWaitCondition>
#include <set>
#include <string>
class WebContext;
class AvatarLoader : public QThread {
Q_OBJECT
private:
struct RequestItem {
std::string email;
QIcon icon;
};
struct Private;
Private *m;
protected:
void run() override;
public:
AvatarLoader();
~AvatarLoader() override;
- QIcon fetch(const std::string &email, bool request) const;
+ QIcon fetch(std::string const &email, bool request) const;
void stop();
void start(WebContext *webcx);
signals:
void updated();
};
#endif // AVATARLOADER_H
diff --git a/src/BasicMainWindow.cpp b/src/BasicMainWindow.cpp
index 5b8b447..b865725 100644
--- a/src/BasicMainWindow.cpp
+++ b/src/BasicMainWindow.cpp
@@ -1,2364 +1,2414 @@
#include "ApplicationGlobal.h"
#include "BasicMainWindow.h"
#include "CheckoutDialog.h"
#include "CloneDialog.h"
#include "CommitDialog.h"
#include "CommitExploreWindow.h"
#include "CommitViewWindow.h"
#include "CreateRepositoryDialog.h"
#include "DeleteBranchDialog.h"
#include "DoYouWantToInitDialog.h"
#include "FileHistoryWindow.h"
#include "FilePropertyDialog.h"
#include "FileUtil.h"
#include "Git.h"
#include "GitDiff.h"
#include "JumpDialog.h"
#include "MemoryReader.h"
#include "MySettings.h"
#include "PushDialog.h"
#include "RepositoryPropertyDialog.h"
#include "SelectCommandDialog.h"
#include "SetGlobalUserDialog.h"
#include "SetUserDialog.h"
#include "SettingsDialog.h"
#include "Terminal.h"
#include "TextEditDialog.h"
#include "WelcomeWizardDialog.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include "gpg.h"
#include "gunzip.h"
#include "platform.h"
#include "webclient.h"
#include <QDesktopServices>
#include <QDir>
#include <QDirIterator>
+#include <QEvent>
#include <QFileIconProvider>
#include <QListWidget>
#include <QMenu>
#include <QMessageBox>
#include <QStandardPaths>
#include <QTreeWidgetItem>
+#include <functional>
#include <memory>
class AsyncExecGitThread_ : public QThread {
private:
GitPtr g;
std::function<void(GitPtr g)> callback;
public:
AsyncExecGitThread_(GitPtr g, std::function<void(GitPtr g)> callback)
: g(g)
, callback(callback)
{
}
protected:
void run() override
{
callback(g);
}
};
-
//
struct BasicMainWindow::Private {
ApplicationSettings appsettings;
QIcon repository_icon;
QIcon folder_icon;
QIcon signature_good_icon;
QIcon signature_dubious_icon;
QIcon signature_bad_icon;
QPixmap transparent_pixmap;
QString starting_dir;
Git::Context gcx;
RepositoryItem current_repo;
QList<RepositoryItem> repos;
Git::CommitItemList logs;
QList<Git::Diff> diff_result;
QStringList added;
QStringList remotes;
QString current_remote_name;
Git::Branch current_branch;
unsigned int temp_file_counter = 0;
std::string ssh_passphrase_user;
std::string ssh_passphrase_pass;
std::string http_uid;
std::string http_pwd;
std::map<QString, GitHubAPI::User> committer_map; // key is email
PtyProcess pty_process;
bool pty_process_ok = false;
PtyCondition pty_condition = PtyCondition::None;
WebContext webcx;
AvatarLoader avatar_loader;
int update_files_list_counter = 0;
int update_commit_table_counter = 0;
bool interaction_canceled = false;
InteractionMode interaction_mode = InteractionMode::None;
QString repository_filter_text;
bool uncommited_changes = false;
std::map<QString, QList<Git::Branch>> branch_map;
std::map<QString, QList<Git::Tag>> tag_map;
std::map<int, QList<Label>> label_map;
std::map<QString, Git::Diff> diff_cache;
GitObjectCache objcache;
bool remote_changed = false;
ServerType server_type = ServerType::Standard;
GitHubRepositoryInfo github;
QString head_id;
bool force_fetch = false;
RepositoryItem temp_repo_for_clone_complete;
+ QVariant pty_process_completion_data;
};
BasicMainWindow::BasicMainWindow(QWidget *parent)
: QMainWindow(parent)
, m(new Private)
{
SettingsDialog::loadSettings(&m->appsettings);
m->starting_dir = QDir::current().absolutePath();
{ // load graphic resources
QFileIconProvider icons;
m->folder_icon = icons.icon(QFileIconProvider::Folder);
m->repository_icon = QIcon(":/image/repository.png");
m->signature_good_icon = QIcon(":/image/signature-good.png");
m->signature_bad_icon = QIcon(":/image/signature-bad.png");
m->signature_dubious_icon = QIcon(":/image/signature-dubious.png");
m->transparent_pixmap = QPixmap(":/image/transparent.png");
}
}
BasicMainWindow::~BasicMainWindow()
{
deleteTempFiles();
delete m;
}
ApplicationSettings *BasicMainWindow::appsettings()
{
return &m->appsettings;
}
const ApplicationSettings *BasicMainWindow::appsettings() const
{
return &m->appsettings;
}
WebContext *BasicMainWindow::webContext()
{
return &m->webcx;
}
QString BasicMainWindow::gitCommand() const
{
return m->gcx.git_command;
}
+namespace {
+
+enum UserEvent {
+ EventUserFunction = QEvent::User,
+};
+
+class UserFunctionEvent : public QEvent {
+public:
+ std::function<void(QVariant &)> func;
+ QVariant var;
+
+ explicit UserFunctionEvent(std::function<void(QVariant const &)> const &func, QVariant const &var)
+ : QEvent((QEvent::Type)EventUserFunction)
+ , func(func)
+ , var(var)
+ {
+ }
+};
+
+} // namespace
+
+bool BasicMainWindow::event(QEvent *event)
+{
+ if (event->type() == EventUserFunction) {
+ if (auto *e = dynamic_cast<UserFunctionEvent *>(event)) {
+ e->func(e->var);
+ return true;
+ }
+ }
+ return QMainWindow::event(event);
+}
+
+void BasicMainWindow::postUserFunctionEvent(const std::function<void(QVariant const &)> &fn, QVariant const &v)
+{
+ qApp->postEvent(this, new UserFunctionEvent(fn, v));
+}
+
void BasicMainWindow::autoOpenRepository(QString dir)
{
auto Open = [&](RepositoryItem const &item){
setCurrentRepository(item, true);
openRepository(false, true);
};
RepositoryItem const *repo = findRegisteredRepository(&dir);
if (repo) {
Open(*repo);
return;
}
RepositoryItem newitem;
GitPtr g = git(dir);
if (isValidWorkingCopy(g)) {
ushort const *left = dir.utf16();
ushort const *right = left + dir.size();
if (right[-1] == '/' || right[-1] == '\\') {
right--;
}
ushort const *p = right;
while (left + 1 < p && !(p[-1] == '/' || p[-1] == '\\')) p--;
if (p < right) {
newitem.local_dir = dir;
newitem.name = QString::fromUtf16(p, right - p);
saveRepositoryBookmark(newitem);
Open(newitem);
return;
}
} else {
DoYouWantToInitDialog dlg(this, dir);
if (dlg.exec() == QDialog::Accepted) {
createRepository(dir);
}
}
}
-GitPtr BasicMainWindow::git(const QString &dir) const
+GitPtr BasicMainWindow::git(QString const &dir) const
{
const_cast<BasicMainWindow *>(this)->checkGitCommand();
GitPtr g = std::make_shared<Git>(m->gcx, dir);
g->setLogCallback(git_callback, (void *)this);
return g;
}
GitPtr BasicMainWindow::git()
{
return git(currentWorkingCopyDir());
}
QPixmap BasicMainWindow::getTransparentPixmap()
{
return m->transparent_pixmap;
}
QIcon BasicMainWindow::committerIcon(int row) const
{
QIcon icon;
if (isAvatarEnabled()) {
auto const &logs = getLogs();
if (row >= 0 && row < (int)logs.size()) {
Git::CommitItem const &commit = logs[row];
if (commit.email.indexOf('@') > 0) {
std::string email = commit.email.toStdString();
icon = getAvatarLoader()->fetch(email, true); // from gavatar
}
}
}
return icon;
}
-const Git::CommitItem *BasicMainWindow::commitItem(int row) const
+Git::CommitItem const *BasicMainWindow::commitItem(int row) const
{
auto const &logs = getLogs();
if (row >= 0 && row < (int)logs.size()) {
return &logs[row];
}
return nullptr;
}
QIcon BasicMainWindow::verifiedIcon(char s) const
{
Git::SignatureGrade g = Git::evaluateSignature(s);
switch (g) {
case Git::SignatureGrade::Good:
return m->signature_good_icon;
case Git::SignatureGrade::Bad:
return m->signature_bad_icon;
case Git::SignatureGrade::Unknown:
case Git::SignatureGrade::Dubious:
case Git::SignatureGrade::Missing:
return m->signature_dubious_icon;
}
return QIcon();
}
QString BasicMainWindow::currentWorkingCopyDir() const
{
QString workdir = m->current_repo.local_dir;
return workdir.isEmpty() ? QString() : workdir;
}
-const QList<BasicMainWindow::Label> *BasicMainWindow::label(int row)
+QList<BasicMainWindow::Label> const *BasicMainWindow::label(int row)
{
auto it = getLabelMap()->find(row);
if (it != getLabelMap()->end()) {
return &it->second;
}
return nullptr;
}
-bool BasicMainWindow::saveAs(const QString &id, const QString &dstpath)
+bool BasicMainWindow::saveAs(QString const &id, QString const &dstpath)
{
if (id.startsWith(PATH_PREFIX)) {
return saveFileAs(id.mid(1), dstpath);
} else {
return saveBlobAs(id, dstpath);
}
}
-bool BasicMainWindow::testRemoteRepositoryValidity(const QString &url)
+bool BasicMainWindow::testRemoteRepositoryValidity(QString const &url)
{
bool ok;
{
OverrideWaitCursor;
ok = isValidRemoteURL(url);
}
QString pass = tr("The URL is a valid repository");
QString fail = tr("Failed to access the URL");
QString text = "%1\n\n%2";
text = text.arg(url).arg(ok ? pass : fail);
QString title = tr("Remote Repository");
if (ok) {
QMessageBox::information(this, title, text);
} else {
QMessageBox::critical(this, title, text);
}
return ok;
}
void BasicMainWindow::addWorkingCopyDir(QString dir, bool open)
{
addWorkingCopyDir(dir, QString(), open);
}
-bool BasicMainWindow::queryCommit(const QString &id, Git::CommitItem *out)
+bool BasicMainWindow::queryCommit(QString const &id, Git::CommitItem *out)
{
*out = Git::CommitItem();
GitPtr g = git();
return g->queryCommit(id, out);
}
QAction *BasicMainWindow::addMenuActionProperty(QMenu *menu)
{
return menu->addAction(tr("&Property"));
}
-void BasicMainWindow::checkout(QWidget *parent, const Git::CommitItem *commit)
+void BasicMainWindow::checkout(QWidget *parent, Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QStringList tags;
QStringList local_branches;
QStringList remote_branches;
{
NamedCommitList named_commits = namedCommitItems(Branches | Tags | Remotes);
JumpDialog::sort(&named_commits);
for (NamedCommitItem const &item : named_commits) {
if (item.id == commit->commit_id) {
QString name = item.name;
if (item.type == NamedCommitItem::Type::Tag) {
tags.push_back(name);
} else if (item.type == NamedCommitItem::Type::Branch) {
int i = name.lastIndexOf('/');
if (i < 0 && name == "HEAD") continue;
if (i > 0 && name.mid(i + 1) == "HEAD") continue;
if (item.remote.isNull()) {
local_branches.push_back(name);
} else {
remote_branches.push_back(name);
}
}
}
}
}
CheckoutDialog dlg(parent, tags, local_branches, remote_branches);
if (dlg.exec() == QDialog::Accepted) {
CheckoutDialog::Operation op = dlg.operation();
QString name = dlg.branchName();
QString id = commit->commit_id;
bool ok = false;
setLogEnabled(g, true);
if (op == CheckoutDialog::Operation::HeadDetached) {
ok = g->git(QString("checkout \"%1\"").arg(id), true);
} else if (op == CheckoutDialog::Operation::CreateLocalBranch) {
ok = g->git(QString("checkout -b \"%1\" \"%2\"").arg(name).arg(id), true);
} else if (op == CheckoutDialog::Operation::ExistingLocalBranch) {
ok = g->git(QString("checkout \"%1\"").arg(name), true);
}
if (ok) {
openRepository(true);
}
}
}
void BasicMainWindow::jumpToCommit(QString id)
{
GitPtr g = git();
id = g->rev_parse(id);
if (!id.isEmpty()) {
int row = rowFromCommitId(id);
setCurrentLogRow(row);
}
}
-void BasicMainWindow::execCommitViewWindow(const Git::CommitItem *commit)
+void BasicMainWindow::execCommitViewWindow(Git::CommitItem const *commit)
{
CommitViewWindow win(this, commit);
win.exec();
}
-void BasicMainWindow::execCommitExploreWindow(QWidget *parent, const Git::CommitItem *commit)
+void BasicMainWindow::execCommitExploreWindow(QWidget *parent, Git::CommitItem const *commit)
{
CommitExploreWindow win(parent, this, getObjCache(), commit);
win.exec();
}
-void BasicMainWindow::execFileHistory(const QString &path)
+void BasicMainWindow::execFileHistory(QString const &path)
{
if (path.isEmpty()) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
FileHistoryWindow dlg(this);
dlg.prepare(g, path);
dlg.exec();
}
void BasicMainWindow::execFilePropertyDialog(QListWidgetItem *item)
{
if (item) {
QString path = getFilePath(item);
QString id = getObjectID(item);
FilePropertyDialog dlg(this);
dlg.exec(this, path, id);
}
}
QString BasicMainWindow::selectGitCommand(bool save)
{
char const *exe = GIT_COMMAND;
QString path = gitCommand();
auto fn = [&](QString const &path){
setGitCommand(path, save);
};
QStringList list = whichCommand_(exe);
#ifdef Q_OS_WIN
{
QStringList newlist;
QString suffix1 = "\\bin\\" GIT_COMMAND;
QString suffix2 = "\\cmd\\" GIT_COMMAND;
for (QString const &s : list) {
newlist.push_back(s);
auto DoIt = [&](QString const &suffix){
if (s.endsWith(suffix)) {
QString t = s.mid(0, s.size() - suffix.size());
QString t1 = t + "\\mingw64\\bin\\" GIT_COMMAND;
if (misc::isExecutable(t1)) newlist.push_back(t1);
QString t2 = t + "\\mingw\\bin\\" GIT_COMMAND;
if (misc::isExecutable(t2)) newlist.push_back(t2);
}
};
DoIt(suffix1);
DoIt(suffix2);
}
std::sort(newlist.begin(), newlist.end());
auto end = std::unique(newlist.begin(), newlist.end());
list.clear();
for (auto it = newlist.begin(); it != end; it++) {
list.push_back(*it);
}
}
#endif
return selectCommand_("Git", exe, list, path, fn);
}
QString BasicMainWindow::selectFileCommand(bool save)
{
char const *exe = FILE_COMMAND;
QString path = global->file_command;
auto fn = [&](QString const &path){
setFileCommand(path, save);
};
QStringList list = whichCommand_(exe);
#ifdef Q_OS_WIN
QString dir = misc::getApplicationDir();
QString path1 = dir / FILE_COMMAND;
QString path2;
int i = dir.lastIndexOf('/');
int j = dir.lastIndexOf('\\');
if (i < j) i = j;
if (i > 0) {
path2 = dir.mid(0, i) / FILE_COMMAND;
}
path1 = misc::normalizePathSeparator(path1);
path2 = misc::normalizePathSeparator(path2);
if (misc::isExecutable(path1)) list.push_back(path1);
if (misc::isExecutable(path2)) list.push_back(path2);
#endif
return selectCommand_("File", exe, list, path, fn);
}
QString BasicMainWindow::selectGpgCommand(bool save)
{
QString path = global->gpg_command;
auto fn = [&](QString const &path){
setGpgCommand(path, save);
};
QStringList list = whichCommand_(GPG_COMMAND, GPG2_COMMAND);
QStringList cmdlist;
cmdlist.push_back(GPG_COMMAND);
cmdlist.push_back(GPG2_COMMAND);
return selectCommand_("GPG", cmdlist, list, path, fn);
}
-const Git::Branch &BasicMainWindow::currentBranch() const
+Git::Branch const &BasicMainWindow::currentBranch() const
{
return m->current_branch;
}
-void BasicMainWindow::setCurrentBranch(const Git::Branch &b)
+void BasicMainWindow::setCurrentBranch(Git::Branch const &b)
{
m->current_branch = b;
}
QString BasicMainWindow::currentRepositoryName() const
{
return m->current_repo.name;
}
QString BasicMainWindow::currentRemoteName() const
{
return m->current_remote_name;
}
QString BasicMainWindow::currentBranchName() const
{
return currentBranch().name;
}
bool BasicMainWindow::isValidWorkingCopy(const GitPtr &g) const
{
return g && g->isValidWorkingCopy();
}
-QString BasicMainWindow::determinFileType_(const QString &path, bool mime, std::function<void (const QString &, QByteArray *)> callback) const
+QString BasicMainWindow::determinFileType_(QString const &path, bool mime, std::function<void (QString const &, QByteArray *)> callback) const
{
const_cast<BasicMainWindow *>(this)->checkFileCommand();
return misc::determinFileType(global->file_command, path, mime, callback);
}
-QString BasicMainWindow::determinFileType(const QString &path, bool mime)
+QString BasicMainWindow::determinFileType(QString const &path, bool mime)
{
return determinFileType_(path, mime, [](QString const &cmd, QByteArray *ba){
misc2::runCommand(cmd, ba);
});
}
QString BasicMainWindow::determinFileType(QByteArray in, bool mime)
{
if (in.isEmpty()) return QString();
if (in.size() > 10) {
if (memcmp(in.data(), "\x1f\x8b\x08", 3) == 0) { // gzip
QBuffer buf;
MemoryReader reader(in.data(), in.size());
reader.open(MemoryReader::ReadOnly);
buf.open(QBuffer::WriteOnly);
gunzip z;
z.set_maximul_size(100000);
z.decode(&reader, &buf);
in = buf.buffer();
}
}
// ファイル名を "-" にすると、リダイレクトで標準入力へ流し込める。
return determinFileType_("-", mime, [&](QString const &cmd, QByteArray *ba){
int r = misc2::runCommand(cmd, &in, ba);
if (r != 0) {
ba->clear();
}
});
}
QList<Git::Tag> BasicMainWindow::queryTagList()
{
QList<Git::Tag> list;
Git::CommitItem const *commit = selectedCommitItem();
if (commit && Git::isValidID(commit->commit_id)) {
list = findTag(commit->commit_id);
}
return list;
}
int BasicMainWindow::limitLogCount() const
{
int n = appsettings()->maximum_number_of_commit_item_acquisitions;
return (n >= 1 && n <= 100000) ? n : 10000;
}
-Git::Object BasicMainWindow::cat_file_(GitPtr g, const QString &id)
+Git::Object BasicMainWindow::cat_file_(GitPtr g, QString const &id)
{
if (isValidWorkingCopy(g)) {
QString path_prefix = PATH_PREFIX;
if (id.startsWith(path_prefix)) {
QString path = g->workingRepositoryDir();
path = path / id.mid(path_prefix.size());
QFile file(path);
if (file.open(QFile::ReadOnly)) {
Git::Object obj;
obj.content = file.readAll();
return obj;
}
} else if (Git::isValidID(id)) {
return getObjCache()->catFile(id);;
}
}
return Git::Object();
}
-Git::Object BasicMainWindow::cat_file(const QString &id)
+Git::Object BasicMainWindow::cat_file(QString const &id)
{
return cat_file_(git(), id);
}
QString BasicMainWindow::newTempFilePath()
{
QString tmpdir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
QString path = tmpdir / tempfileHeader() + QString::number(m->temp_file_counter);
m->temp_file_counter++;
return path;
}
-QString BasicMainWindow::findFileID(GitPtr, const QString &commit_id, const QString &file)
+QString BasicMainWindow::findFileID(GitPtr, QString const &commit_id, QString const &file)
{
return lookupFileID(getObjCache(), commit_id, file);
}
void BasicMainWindow::updateFilesList(QString const &id, QList<Git::Diff> *diff_list, QListWidget *listwidget)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
listwidget->clear();
auto AddItem = [&](QString const &filename, QString header, int idiff){
if (header.isEmpty()) {
header = "(??\?) "; // damn trigraph
}
QListWidgetItem *item = new QListWidgetItem(header + filename);
item->setData(FilePathRole, filename);
item->setData(DiffIndexRole, idiff);
item->setData(HunkIndexRole, -1);
listwidget->addItem(item);
};
GitDiff dm(getObjCache());
if (!dm.diff(id, diff_list)) return;
addDiffItems(diff_list, AddItem);
}
void BasicMainWindow::writeLog(const char *ptr, int len)
{
internalWriteLog(ptr, len);
}
void BasicMainWindow::setAppSettings(const ApplicationSettings &appsettings)
{
m->appsettings = appsettings;
}
QPixmap BasicMainWindow::getTransparentPixmap() const
{
return m->transparent_pixmap;
}
QIcon BasicMainWindow::getSignatureBadIcon() const
{
return m->signature_bad_icon;
}
QIcon BasicMainWindow::getSignatureDubiousIcon() const
{
return m->signature_dubious_icon;
}
QIcon BasicMainWindow::getSignatureGoodIcon() const
{
return m->signature_good_icon;
}
QIcon BasicMainWindow::getFolderIcon() const
{
return m->folder_icon;
}
QIcon BasicMainWindow::getRepositoryIcon() const
{
return m->repository_icon;
}
const Git::CommitItemList &BasicMainWindow::getLogs() const
{
return m->logs;
}
Git::CommitItem const *BasicMainWindow::getLog(int index) const
{
return (index >= 0 && index < (int)m->logs.size()) ? &m->logs[index] : nullptr;
}
Git::CommitItemList *BasicMainWindow::getLogsPtr()
{
return &m->logs;
}
void BasicMainWindow::setLogs(const Git::CommitItemList &logs)
{
m->logs = logs;
}
void BasicMainWindow::clearLogs()
{
m->logs.clear();
}
BasicMainWindow::PtyCondition BasicMainWindow::getPtyCondition()
{
return m->pty_condition;
}
+void BasicMainWindow::setPtyUserData(QVariant const &userdata)
+{
+ m->pty_process.setVariant(userdata);
+}
+
void BasicMainWindow::setPtyCondition(const PtyCondition &ptyCondition)
{
m->pty_condition = ptyCondition;
}
+
PtyProcess *BasicMainWindow::getPtyProcess()
{
return &m->pty_process;
}
bool BasicMainWindow::getPtyProcessOk() const
{
return m->pty_process_ok;
}
void BasicMainWindow::setPtyProcessOk(bool pty_process_ok)
{
m->pty_process_ok = pty_process_ok;
}
const QList<RepositoryItem> &BasicMainWindow::getRepos() const
{
return m->repos;
}
QList<RepositoryItem> *BasicMainWindow::getReposPtr()
{
return &m->repos;
}
-void BasicMainWindow::setCurrentRemoteName(const QString &name)
+void BasicMainWindow::setCurrentRemoteName(QString const &name)
{
m->current_remote_name = name;
}
AvatarLoader *BasicMainWindow::getAvatarLoader()
{
return &m->avatar_loader;
}
AvatarLoader const *BasicMainWindow::getAvatarLoader() const
{
return &m->avatar_loader;
}
int *BasicMainWindow::ptrUpdateFilesListCounter()
{
return &m->update_files_list_counter;
}
int *BasicMainWindow::ptrUpdateCommitTableCounter()
{
return &m->update_commit_table_counter;
}
bool BasicMainWindow::interactionCanceled() const
{
return m->interaction_canceled;
}
void BasicMainWindow::setInteractionCanceled(bool canceled)
{
m->interaction_canceled = canceled;
}
BasicMainWindow::InteractionMode BasicMainWindow::interactionMode() const
{
return m->interaction_mode;
}
void BasicMainWindow::setInteractionMode(const InteractionMode &im)
{
m->interaction_mode = im;
}
QString BasicMainWindow::getRepositoryFilterText() const
{
return m->repository_filter_text;
}
-void BasicMainWindow::setRepositoryFilterText(const QString &text)
+void BasicMainWindow::setRepositoryFilterText(QString const &text)
{
m->repository_filter_text = text;
}
void BasicMainWindow::setUncommitedChanges(bool uncommited_changes)
{
m->uncommited_changes = uncommited_changes;
}
QList<Git::Diff> *BasicMainWindow::diffResult()
{
return &m->diff_result;
}
std::map<QString, Git::Diff> *BasicMainWindow::getDiffCacheMap()
{
return &m->diff_cache;
}
bool BasicMainWindow::getRemoteChanged() const
{
return m->remote_changed;
}
void BasicMainWindow::setRemoteChanged(bool remote_changed)
{
m->remote_changed = remote_changed;
}
void BasicMainWindow::setServerType(const ServerType &server_type)
{
m->server_type = server_type;
}
GitHubRepositoryInfo *BasicMainWindow::ptrGitHub()
{
return &m->github;
}
std::map<int, QList<BasicMainWindow::Label>> *BasicMainWindow::getLabelMap()
{
return &m->label_map;
}
void BasicMainWindow::clearLabelMap()
{
m->label_map.clear();
}
GitObjectCache *BasicMainWindow::getObjCache()
{
return &m->objcache;
}
bool BasicMainWindow::getForceFetch() const
{
return m->force_fetch;
}
void BasicMainWindow::setForceFetch(bool force_fetch)
{
m->force_fetch = force_fetch;
}
std::map<QString, QList<Git::Tag>> *BasicMainWindow::ptrTagMap()
{
return &m->tag_map;
}
QString BasicMainWindow::getHeadId() const
{
return m->head_id;
}
-void BasicMainWindow::setHeadId(const QString &head_id)
+void BasicMainWindow::setHeadId(QString const &head_id)
{
m->head_id = head_id;
}
-const RepositoryItem &BasicMainWindow::getTempRepoForCloneComplete() const
+void BasicMainWindow::setPtyProcessCompletionData(QVariant const &value)
{
- return m->temp_repo_for_clone_complete;
+ m->pty_process_completion_data = value;
+}
+
+QVariant const &BasicMainWindow::getTempRepoForCloneCompleteV() const
+{
+ return m->pty_process_completion_data;
}
bool BasicMainWindow::git_callback(void *cookie, const char *ptr, int len)
{
auto *mw = (BasicMainWindow *)cookie;
mw->emitWriteLog(QByteArray(ptr, len));
return true;
}
-QString BasicMainWindow::selectCommand_(const QString &cmdname, const QStringList &cmdfiles, const QStringList &list, QString path, std::function<void (const QString &)> const &callback)
+QString BasicMainWindow::selectCommand_(QString const &cmdname, QStringList const &cmdfiles, QStringList const &list, QString path, std::function<void (QString const &)> const &callback)
{
QString window_title = tr("Select %1 command");
window_title = window_title.arg(cmdfiles.front());
SelectCommandDialog dlg(this, cmdname, cmdfiles, path, list);
dlg.setWindowTitle(window_title);
if (dlg.exec() == QDialog::Accepted) {
path = dlg.selectedFile();
path = misc::normalizePathSeparator(path);
QFileInfo info(path);
if (info.isExecutable()) {
callback(path);
return path;
}
}
return QString();
}
-QString BasicMainWindow::selectCommand_(const QString &cmdname, const QString &cmdfile, const QStringList &list, QString const &path, std::function<void (const QString &)> const &callback)
+QString BasicMainWindow::selectCommand_(QString const &cmdname, QString const &cmdfile, QStringList const &list, QString const &path, std::function<void (QString const &)> const &callback)
{
QStringList cmdfiles;
cmdfiles.push_back(cmdfile);
return selectCommand_(cmdname, cmdfiles, list, path, callback);
}
bool BasicMainWindow::checkGitCommand()
{
while (1) {
if (misc::isExecutable(gitCommand())) {
return true;
}
if (selectGitCommand(true).isEmpty()) {
close();
return false;
}
}
}
bool BasicMainWindow::checkFileCommand()
{
while (1) {
if (misc::isExecutable(global->file_command)) {
return true;
}
if (selectFileCommand(true).isEmpty()) {
close();
return false;
}
}
}
-bool BasicMainWindow::checkExecutable(const QString &path)
+bool BasicMainWindow::checkExecutable(QString const &path)
{
if (QFileInfo(path).isExecutable()) {
return true;
}
QString text = "The specified program '%1' is not executable.\n";
text = text.arg(path);
writeLog(text);
return false;
}
-void BasicMainWindow::internalSetCommand(const QString &path, bool save, const QString &name, QString *out)
+void BasicMainWindow::internalSetCommand(QString const &path, bool save, QString const &name, QString *out)
{
if (!path.isEmpty() && checkExecutable(path)) {
if (save) {
MySettings s;
s.beginGroup("Global");
s.setValue(name, path);
s.endGroup();
}
*out = path;
} else {
*out = QString();
}
}
-QStringList BasicMainWindow::whichCommand_(const QString &cmdfile1, const QString &cmdfile2)
+QStringList BasicMainWindow::whichCommand_(QString const &cmdfile1, QString const &cmdfile2)
{
QStringList list;
if (!cmdfile1.isEmpty()){
std::vector<std::string> vec;
FileUtil::which(cmdfile1.toStdString(), &vec);
for (std::string const &s : vec) {
list.push_back(QString::fromStdString(s));
}
}
if (!cmdfile2.isEmpty()){
std::vector<std::string> vec;
FileUtil::which(cmdfile2.toStdString(), &vec);
for (std::string const &s : vec) {
list.push_back(QString::fromStdString(s));
}
}
return list;
}
void BasicMainWindow::emitWriteLog(QByteArray ba)
{
emit signalWriteLog(ba);
}
-void BasicMainWindow::setWindowTitle_(const Git::User &user)
+void BasicMainWindow::setWindowTitle_(Git::User const &user)
{
if (user.name.isEmpty() && user.email.isEmpty()) {
setWindowTitle(qApp->applicationName());
} else {
setWindowTitle(QString("%1 : %2 <%3>")
.arg(qApp->applicationName())
.arg(user.name)
.arg(user.email)
);
}
}
bool BasicMainWindow::execSetGlobalUserDialog()
{
SetGlobalUserDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
GitPtr g = git();
Git::User user = dlg.user();
g->setUser(user, true);
updateWindowTitle(g);
return true;
}
return false;
}
-bool BasicMainWindow::saveByteArrayAs(const QByteArray &ba, const QString &dstpath)
+bool BasicMainWindow::saveByteArrayAs(const QByteArray &ba, QString const &dstpath)
{
QFile file(dstpath);
if (file.open(QFile::WriteOnly)) {
file.write(ba);
file.close();
return true;
}
QString msg = "Failed to open the file '%1' for write";
msg = msg.arg(dstpath);
qDebug() << msg;
return false;
}
-bool BasicMainWindow::saveFileAs(const QString &srcpath, const QString &dstpath)
+bool BasicMainWindow::saveFileAs(QString const &srcpath, QString const &dstpath)
{
QFile f(srcpath);
if (f.open(QFile::ReadOnly)) {
QByteArray ba = f.readAll();
if (saveByteArrayAs(ba, dstpath)) {
return true;
}
} else {
QString msg = "Failed to open the file '%1' for read";
msg = msg.arg(srcpath);
qDebug() << msg;
}
return false;
}
-bool BasicMainWindow::saveBlobAs(const QString &id, const QString &dstpath)
+bool BasicMainWindow::saveBlobAs(QString const &id, QString const &dstpath)
{
Git::Object obj = cat_file(id);
if (!obj.content.isEmpty()) {
if (saveByteArrayAs(obj.content, dstpath)) {
return true;
}
} else {
QString msg = "Failed to get the content of the object '%1'";
msg = msg.arg(id);
qDebug() << msg;
}
return false;
}
void BasicMainWindow::revertAllFiles()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QString cmd = "git reset --hard HEAD";
if (askAreYouSureYouWantToRun(tr("Revert all files"), "> " + cmd)) {
g->resetAllFiles();
openRepository(true);
}
}
-void BasicMainWindow::deleteTags(const Git::CommitItem &commit)
+void BasicMainWindow::deleteTags(Git::CommitItem const &commit)
{
auto it = ptrTagMap()->find(commit.commit_id);
if (it != ptrTagMap()->end()) {
QStringList names;
QList<Git::Tag> const &tags = it->second;
for (Git::Tag const &tag : tags) {
names.push_back(tag.name);
}
deleteTags(names);
}
}
bool BasicMainWindow::isAvatarEnabled() const
{
return appsettings()->get_committer_icon;
}
bool BasicMainWindow::isGitHub() const
{
return m->server_type == ServerType::GitHub;
}
QStringList BasicMainWindow::remotes() const
{
return m->remotes;
}
-QList<Git::Branch> BasicMainWindow::findBranch(const QString &id)
+QList<Git::Branch> BasicMainWindow::findBranch(QString const &id)
{
auto it = branchMapRef().find(id);
if (it != branchMapRef().end()) {
return it->second;
}
return QList<Git::Branch>();
}
-QString BasicMainWindow::saveAsTemp(const QString &id)
+QString BasicMainWindow::saveAsTemp(QString const &id)
{
QString path = newTempFilePath();
saveAs(id, path);
return path;
}
QString BasicMainWindow::tempfileHeader() const
{
QString name = "jp_soramimi_Guitar_%1_";
return name.arg(QApplication::applicationPid());
}
void BasicMainWindow::deleteTempFiles()
{
QString dir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
QString name = tempfileHeader();
QDirIterator it(dir, { name + "*" });
while (it.hasNext()) {
QString path = it.next();
QFile::remove(path);
}
}
-QString BasicMainWindow::getCommitIdFromTag(const QString &tag)
+QString BasicMainWindow::getCommitIdFromTag(QString const &tag)
{
return getObjCache()->getCommitIdFromTag(tag);
}
-bool BasicMainWindow::isValidRemoteURL(const QString &url)
+bool BasicMainWindow::isValidRemoteURL(QString const &url)
{
if (url.indexOf('\"') >= 0) {
return false;
}
stopPtyProcess();
GitPtr g = git(QString());
QString cmd = "ls-remote \"%1\" HEAD";
bool f = g->git(cmd.arg(url), false, false, getPtyProcess());
{
QTime time;
time.start();
while (!getPtyProcess()->wait(10)) {
if (time.elapsed() > 10000) {
f = false;
break;
}
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
stopPtyProcess();
}
QString line = g->resultText().trimmed();
QString head;
int i = line.indexOf('\t');
if (i == GIT_ID_LENGTH) {
QString id = line.mid(0, i);
QString name = line.mid(i + 1);
qDebug() << id << name;
if (name == "HEAD" && Git::isValidID(id)) {
head = id;
}
}
if (f) {
qDebug() << "This is a valid repository.";
if (head.isEmpty()) {
qDebug() << "But HEAD not found";
}
return true;
}
qDebug() << "It is not a repository.";
return false;
}
void BasicMainWindow::addWorkingCopyDir(QString dir, QString name, bool open)
{
if (dir.endsWith(".git")) {
int i = dir.size();
if (i > 4) {
ushort c = dir.utf16()[i - 5];
if (c == '/' || c == '\\') {
dir = dir.mid(0, i - 5);
}
}
}
if (!Git::isValidWorkingCopy(dir)) {
qDebug() << "Invalid working dir: " + dir;
return;
}
if (name.isEmpty()) {
name = makeRepositoryName(dir);
}
RepositoryItem item;
item.local_dir = dir;
item.name = name;
saveRepositoryBookmark(item);
if (open) {
setCurrentRepository(item, true);
GitPtr g = git(item.local_dir);
openRepository_(g);
}
}
-QString BasicMainWindow::makeRepositoryName(const QString &loc)
+QString BasicMainWindow::makeRepositoryName(QString const &loc)
{
int i = loc.lastIndexOf('/');
int j = loc.lastIndexOf('\\');
if (i < j) i = j;
if (i >= 0) {
i++;
j = loc.size();
if (loc.endsWith(".git")) {
j -= 4;
}
return loc.mid(i, j - i);
}
return QString();
}
void BasicMainWindow::clearAuthentication()
{
clearSshAuthentication();
m->http_uid.clear();
m->http_pwd.clear();
}
void BasicMainWindow::onAvatarUpdated()
{
updateCommitTableLater();
}
void BasicMainWindow::queryRemotes(GitPtr g)
{
m->remotes = g->getRemotes();
std::sort(m->remotes.begin(), m->remotes.end());
}
-bool BasicMainWindow::runOnRepositoryDir(std::function<void (QString)> callback, const RepositoryItem *repo)
+bool BasicMainWindow::runOnRepositoryDir(std::function<void (QString)> callback, RepositoryItem const *repo)
{
if (!repo) {
repo = &m->current_repo;
}
QString dir = repo->local_dir;
dir.replace('\\', '/');
if (QFileInfo(dir).isDir()) {
callback(dir);
return true;
}
QMessageBox::warning(this, qApp->applicationName(), tr("No repository selected"));
return false;
}
void BasicMainWindow::clearSshAuthentication()
{
m->ssh_passphrase_user.clear();
m->ssh_passphrase_pass.clear();
}
QString BasicMainWindow::getFilePath(QListWidgetItem *item)
{
if (!item) return QString();
return item->data(FilePathRole).toString();
}
bool BasicMainWindow::isGroupItem(QTreeWidgetItem *item)
{
if (item) {
int index = item->data(0, IndexRole).toInt();
if (index == GroupItem) {
return true;
}
}
return false;
}
int BasicMainWindow::indexOfLog(QListWidgetItem *item)
{
if (!item) return -1;
return item->data(IndexRole).toInt();
}
int BasicMainWindow::indexOfDiff(QListWidgetItem *item)
{
if (!item) return -1;
return item->data(DiffIndexRole).toInt();
}
int BasicMainWindow::getHunkIndex(QListWidgetItem *item)
{
if (!item) return -1;
return item->data(HunkIndexRole).toInt();
}
void BasicMainWindow::initNetworking()
{
std::string http_proxy;
std::string https_proxy;
if (appsettings()->proxy_type == "auto") {
#ifdef Q_OS_WIN
http_proxy = misc::makeProxyServerURL(getWin32HttpProxy().toStdString());
#else
auto getienv = [](std::string const &name)->char const *{
char **p = environ;
while (*p) {
if (strncasecmp(*p, name.c_str(), name.size()) == 0) {
char const *e = *p + name.size();
if (*e == '=') {
return e + 1;
}
}
p++;
}
return nullptr;
};
char const *p;
p = getienv("http_proxy");
if (p) {
http_proxy = misc::makeProxyServerURL(std::string(p));
}
p = getienv("https_proxy");
if (p) {
https_proxy = misc::makeProxyServerURL(std::string(p));
}
#endif
} else if (appsettings()->proxy_type == "manual") {
http_proxy = appsettings()->proxy_server.toStdString();
}
webContext()->set_http_proxy(http_proxy);
webContext()->set_https_proxy(https_proxy);
}
-QString BasicMainWindow::abbrevCommitID(const Git::CommitItem &commit)
+QString BasicMainWindow::abbrevCommitID(Git::CommitItem const &commit)
{
return commit.commit_id.mid(0, 7);
}
bool BasicMainWindow::saveRepositoryBookmarks() const
{
QString path = getBookmarksFilePath();
return RepositoryBookmark::save(path, &getRepos());
}
QString BasicMainWindow::getBookmarksFilePath() const
{
return global->app_config_dir / "bookmarks.xml";
}
void BasicMainWindow::stopPtyProcess()
{
getPtyProcess()->stop();
QDir::setCurrent(m->starting_dir);
}
void BasicMainWindow::abortPtyProcess()
{
stopPtyProcess();
setPtyProcessOk(false);
setInteractionCanceled(true);
}
bool BasicMainWindow::execWelcomeWizardDialog()
{
WelcomeWizardDialog dlg(this);
dlg.set_git_command_path(appsettings()->git_command);
dlg.set_file_command_path(appsettings()->file_command);
dlg.set_default_working_folder(appsettings()->default_working_dir);
if (misc::isExecutable(appsettings()->git_command)) {
gitCommand() = appsettings()->git_command;
Git g(m->gcx, QString());
Git::User user = g.getUser(Git::Source::Global);
dlg.set_user_name(user.name);
dlg.set_user_email(user.email);
}
if (dlg.exec() == QDialog::Accepted) {
appsettings()->git_command = gitCommand() = dlg.git_command_path();
appsettings()->file_command = global->file_command = dlg.file_command_path();
appsettings()->default_working_dir = dlg.default_working_folder();
SettingsDialog::saveSettings(&m->appsettings);
if (misc::isExecutable(appsettings()->git_command)) {
GitPtr g = git();
Git::User user;
user.name = dlg.user_name();
user.email = dlg.user_email();
g->setUser(user, true);
}
return true;
}
return false;
}
void BasicMainWindow::execRepositoryPropertyDialog(QString workdir, bool open_repository_menu)
{
if (workdir.isEmpty()) {
workdir = currentWorkingCopyDir();
}
QString name;
RepositoryItem const *repo = findRegisteredRepository(&workdir);
if (repo) {
name = repo->name;
} else {
QMessageBox::warning(this, tr("Repository Property"), tr("Not a valid git repository") + "\n\n" + workdir);
return;
}
if (name.isEmpty()) {
name = makeRepositoryName(workdir);
}
GitPtr g = git(workdir);
RepositoryPropertyDialog dlg(this, g, *repo, open_repository_menu);
dlg.exec();
if (dlg.isRemoteChanged()) {
emit remoteInfoChanged();
}
}
-void BasicMainWindow::execSetUserDialog(const Git::User &global_user, const Git::User &repo_user, const QString &reponame)
+void BasicMainWindow::execSetUserDialog(Git::User const &global_user, Git::User const &repo_user, QString const &reponame)
{
SetUserDialog dlg(this, global_user, repo_user, reponame);
if (dlg.exec() == QDialog::Accepted) {
GitPtr g = git();
Git::User user = dlg.user();
if (dlg.isGlobalChecked()) {
g->setUser(user, true);
}
if (dlg.isRepositoryChecked()) {
g->setUser(user, false);
}
updateWindowTitle(g);
}
}
-void BasicMainWindow::setGitCommand(const QString &path, bool save)
+void BasicMainWindow::setGitCommand(QString const &path, bool save)
{
internalSetCommand(path, save, "GitCommand", &m->gcx.git_command);
}
-void BasicMainWindow::setFileCommand(const QString &path, bool save)
+void BasicMainWindow::setFileCommand(QString const &path, bool save)
{
internalSetCommand(path, save, "FileCommand", &global->file_command);
}
-void BasicMainWindow::setGpgCommand(const QString &path, bool save)
+void BasicMainWindow::setGpgCommand(QString const &path, bool save)
{
internalSetCommand(path, save, "GpgCommand", &global->gpg_command);
if (!global->gpg_command.isEmpty()) {
GitPtr g = git();
g->configGpgProgram(global->gpg_command, true);
}
}
void BasicMainWindow::logGitVersion()
{
GitPtr g = git();
QString s = g->version();
if (!s.isEmpty()) {
s += '\n';
writeLog(s);
}
}
void BasicMainWindow::setUnknownRepositoryInfo()
{
setRepositoryInfo("---", "");
Git g(m->gcx, QString());
Git::User user = g.getUser(Git::Source::Global);
setWindowTitle_(user);
}
void BasicMainWindow::internalClearRepositoryInfo()
{
setHeadId(QString());
setCurrentBranch(Git::Branch());
setServerType(ServerType::Standard);
m->github = GitHubRepositoryInfo();
}
void BasicMainWindow::checkUser()
{
Git g(m->gcx, QString());
while (1) {
Git::User user = g.getUser(Git::Source::Global);
if (!user.name.isEmpty() && !user.email.isEmpty()) {
return; // ok
}
if (!execSetGlobalUserDialog()) {
return;
}
}
}
void BasicMainWindow::openRepository(bool validate, bool waitcursor)
{
if (validate) {
QString dir = currentWorkingCopyDir();
if (!QFileInfo(dir).isDir()) {
int r = QMessageBox::warning(this, tr("Open Repository"), dir + "\n\n" + tr("No such folder") + "\n\n" + tr("Remove from bookmark ?"), QMessageBox::Ok, QMessageBox::Cancel);
if (r == QMessageBox::Ok) {
removeSelectedRepositoryFromBookmark(false);
}
return;
}
if (!Git::isValidWorkingCopy(dir)) {
QMessageBox::warning(this, tr("Open Repository"), tr("Not a valid git repository") + "\n\n" + dir);
return;
}
}
if (waitcursor) {
OverrideWaitCursor;
openRepository(false, false);
return;
}
GitPtr g = git(); // ポインタの有効性チェックはしない(nullptrでも続行)
openRepository_(g);
}
void BasicMainWindow::reopenRepository(bool log, std::function<void (GitPtr)> callback)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
OverrideWaitCursor;
if (log) {
setLogEnabled(g, true);
AsyncExecGitThread_ th(g, callback);
th.start();
while (1) {
if (th.wait(1)) break;
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
setLogEnabled(g, false);
} else {
callback(g);
}
openRepository_(g);
}
void BasicMainWindow::openSelectedRepository()
{
RepositoryItem const *repo = selectedRepositoryItem();
if (repo) {
setCurrentRepository(*repo, true);
openRepository(true);
}
}
void BasicMainWindow::checkRemoteUpdate()
{
if (getPtyProcess()->isRunning()) return;
emit signalCheckRemoteUpdate();
}
bool BasicMainWindow::isThereUncommitedChanges() const
{
return m->uncommited_changes;
}
bool BasicMainWindow::makeDiff(QString id, QList<Git::Diff> *out)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return false;
Git::FileStatusList list = g->status();
setUncommitedChanges(!list.empty());
if (id.isEmpty() && !isThereUncommitedChanges()) {
id = getObjCache()->revParse("HEAD");
}
bool uncommited = (id.isEmpty() && isThereUncommitedChanges());
GitDiff dm(getObjCache());
if (uncommited) {
dm.diff_uncommited(out);
} else {
dm.diff(id, out);
}
return true; // success
}
-void BasicMainWindow::addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (const QString &, QString, int)> &add_item)
+void BasicMainWindow::addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (QString const &, QString, int)> &add_item)
{
for (int idiff = 0; idiff < diff_list->size(); idiff++) {
Git::Diff const &diff = diff_list->at(idiff);
QString header;
switch (diff.type) {
case Git::Diff::Type::Modify: header = "(chg) "; break;
case Git::Diff::Type::Copy: header = "(cpy) "; break;
case Git::Diff::Type::Rename: header = "(ren) "; break;
case Git::Diff::Type::Create: header = "(add) "; break;
case Git::Diff::Type::Delete: header = "(del) "; break;
case Git::Diff::Type::ChType: header = "(chg) "; break;
case Git::Diff::Type::Unmerged: header = "(unmerged) "; break;
}
add_item(diff.path, header, idiff);
}
}
Git::CommitItemList BasicMainWindow::retrieveCommitLog(GitPtr g)
{
Git::CommitItemList list = g->log(limitLogCount());
// 親子関係を調べて、順番が狂っていたら、修正する。
std::set<QString> set;
size_t i = 0;
while (i < list.size()) {
size_t newpos = -1;
for (QString const &parent : list[i].parent_ids) {
auto it = set.find(parent);
if (it != set.end()) {
for (size_t j = 0; j < i; j++) {
if (parent == list[j].commit_id) {
if (newpos == (size_t)-1 || j < newpos) {
newpos = j;
}
qDebug() << "fix commit order" << parent;
break;
}
}
}
}
set.insert(set.end(), list[i].commit_id);
if (newpos != (size_t)-1) {
Git::CommitItem t = list[newpos];
list.erase(list.begin() + newpos);
list.insert(list.begin() + i, t);
}
i++;
}
return list;
}
void BasicMainWindow::queryBranches(GitPtr g)
{
Q_ASSERT(g);
m->branch_map.clear();
QList<Git::Branch> branches = g->branches();
for (Git::Branch const &b : branches) {
if (b.isCurrent()) {
setCurrentBranch(b);
}
branchMapRef()[b.id].append(b);
}
}
std::map<QString, QList<Git::Branch> > &BasicMainWindow::branchMapRef()
{
return m->branch_map;
}
void BasicMainWindow::updateCommitTableLater()
{
*ptrUpdateCommitTableCounter() = 200;
}
void BasicMainWindow::updateRemoteInfo()
{
queryRemotes(git());
m->current_remote_name = QString();
{
Git::Branch const &r = currentBranch();
m->current_remote_name = r.remote;
}
if (m->current_remote_name.isEmpty()) {
if (m->remotes.size() == 1) {
m->current_remote_name = m->remotes[0];
}
}
emit remoteInfoChanged();
}
void BasicMainWindow::updateWindowTitle(GitPtr g)
{
if (isValidWorkingCopy(g)) {
Git::User user = g->getUser(Git::Source::Default);
setWindowTitle_(user);
} else {
setUnknownRepositoryInfo();
}
}
QString BasicMainWindow::makeCommitInfoText(int row, QList<BasicMainWindow::Label> *label_list)
{
QString message_ex;
Git::CommitItem const *commit = &getLogs()[row];
{ // branch
if (label_list) {
if (commit->commit_id == getHeadId()) {
Label label(Label::Head);
label.text = "HEAD";
label_list->push_back(label);
}
}
QList<Git::Branch> list = findBranch(commit->commit_id);
for (Git::Branch const &b : list) {
if (b.flags & Git::Branch::HeadDetachedAt) continue;
if (b.flags & Git::Branch::HeadDetachedFrom) continue;
Label label(Label::LocalBranch);
label.text = b.name;
if (!b.remote.isEmpty()) {
label.kind = Label::RemoteBranch;
label.text = "remotes" / b.remote / label.text;
}
if (b.ahead > 0) {
label.text += tr(", %1 ahead").arg(b.ahead);
}
if (b.behind > 0) {
label.text += tr(", %1 behind").arg(b.behind);
}
message_ex += " {" + label.text + '}';
if (label_list) label_list->push_back(label);
}
}
{ // tag
QList<Git::Tag> list = findTag(commit->commit_id);
for (Git::Tag const &t : list) {
Label label(Label::Tag);
label.text = t.name;
message_ex += QString(" {#%1}").arg(label.text);
if (label_list) label_list->push_back(label);
}
}
return message_ex;
}
void BasicMainWindow::removeRepositoryFromBookmark(int index, bool ask)
{
if (ask) {
int r = QMessageBox::warning(this, tr("Confirm Remove"), tr("Are you sure you want to remove the repository from bookmarks ?") + '\n' + tr("(Files will NOT be deleted)"), QMessageBox::Ok, QMessageBox::Cancel);
if (r != QMessageBox::Ok) return;
}
auto *repos = getReposPtr();
if (index >= 0 && index < repos->size()) {
repos->erase(repos->begin() + index);
saveRepositoryBookmarks();
updateRepositoriesList();
}
}
void BasicMainWindow::clone()
{
if (!isRemoteOnline()) return;
QString url;
QString dir = defaultWorkingDir();
while (1) {
CloneDialog dlg(this, url, dir);
if (dlg.exec() != QDialog::Accepted) {
return;
}
url = dlg.url();
dir = dlg.dir();
if (dlg.action() == CloneDialog::Action::Clone) {
// 既存チェック
QFileInfo info(dir);
if (info.isFile()) {
QString msg = dir + "\n\n" + tr("A file with same name already exists");
QMessageBox::warning(this, tr("Clone"), msg);
continue;
}
if (info.isDir()) {
QString msg = dir + "\n\n" + tr("A folder with same name already exists");
QMessageBox::warning(this, tr("Clone"), msg);
continue;
}
// クローン先ディレクトリを求める
Git::CloneData clone_data = Git::preclone(url, dir);
// クローン先ディレクトリの存在チェック
QString basedir = misc::normalizePathSeparator(clone_data.basedir);
if (!QFileInfo(basedir).isDir()) {
int i = basedir.indexOf('/');
int j = basedir.indexOf('\\');
if (i < j) i = j;
if (i < 0) {
QString msg = basedir + "\n\n" + tr("Invalid folder");
QMessageBox::warning(this, tr("Clone"), msg);
continue;
}
QString msg = basedir + "\n\n" + tr("No such folder. Create it now ?");
if (QMessageBox::warning(this, tr("Clone"), msg, QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) {
continue;
}
// ディレクトリを作成
QString base = basedir.mid(0, i + 1);
QString sub = basedir.mid(i + 1);
QDir(base).mkpath(sub);
}
- m->temp_repo_for_clone_complete.local_dir = dir;
- m->temp_repo_for_clone_complete.local_dir.replace('\\', '/');
- m->temp_repo_for_clone_complete.name = makeRepositoryName(dir);
+ RepositoryItem data;
+ data.local_dir = dir;
+ data.local_dir.replace('\\', '/');
+ data.name = makeRepositoryName(dir);
GitPtr g = git(QString());
+ setPtyUserData(QVariant::fromValue<RepositoryItem>(data));
setPtyCondition(PtyCondition::Clone);
setPtyProcessOk(true);
g->clone(clone_data, getPtyProcess());
-
} else if (dlg.action() == CloneDialog::Action::AddExisting) {
addWorkingCopyDir(dir, true);
}
return; // done
}
}
void BasicMainWindow::checkout()
{
checkout(this, selectedCommitItem());
}
void BasicMainWindow::commit(bool amend)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
while (1) {
Git::User user = g->getUser(Git::Source::Default);
QString sign_id = g->signingKey(Git::Source::Default);
gpg::Data key;
{
QList<gpg::Data> keys;
gpg::listKeys(global->gpg_command, &keys);
for (gpg::Data const &k : keys) {
if (k.id == sign_id) {
key = k;
}
}
}
CommitDialog dlg(this, currentRepositoryName(), user, key);
if (amend) {
dlg.setText(getLogs()[0].message);
}
if (dlg.exec() == QDialog::Accepted) {
QString text = dlg.text();
if (text.isEmpty()) {
QMessageBox::warning(this, tr("Commit"), tr("Commit message can not be omitted."));
continue;
}
bool sign = dlg.isSigningEnabled();
bool ok;
if (amend) {
ok = g->commit_amend_m(text, sign, getPtyProcess());
} else {
ok = g->commit(text, sign, getPtyProcess());
}
if (ok) {
setForceFetch(true);
openRepository(true);
} else {
QString err = g->errorMessage().trimmed();
err += "\n*** ";
err += tr("Failed to commit");
err += " ***\n";
writeLog(err);
}
}
break;
}
}
void BasicMainWindow::commitAmend()
{
commit(true);
}
-void BasicMainWindow::pushSetUpstream(const QString &remote, const QString &branch)
+void BasicMainWindow::pushSetUpstream(QString const &remote, QString const &branch)
{
if (remote.isEmpty()) return;
if (branch.isEmpty()) return;
int exitcode = 0;
QString errormsg;
reopenRepository(true, [&](GitPtr g){
g->push_u(remote, branch, getPtyProcess());
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
exitcode = getPtyProcess()->getExitCode();
errormsg = getPtyProcess()->getMessage();
});
if (exitcode == 128) {
if (errormsg.indexOf("Connection refused") >= 0) {
QMessageBox::critical(this, qApp->applicationName(), tr("Connection refused."));
return;
}
}
updateRemoteInfo();
}
bool BasicMainWindow::pushSetUpstream(bool testonly)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return false;
QStringList remotes = g->getRemotes();
QString current_branch = currentBranchName();
QStringList branches;
for (Git::Branch const &b : g->branches()) {
branches.push_back(b.name);
}
if (remotes.isEmpty() || branches.isEmpty()) {
return false;
}
if (testonly) {
return true;
}
PushDialog dlg(this, remotes, branches, PushDialog::RemoteBranch(QString(), current_branch));
if (dlg.exec() == QDialog::Accepted) {
PushDialog::Action a = dlg.action();
if (a == PushDialog::PushSimple) {
push();
} else if (a == PushDialog::PushSetUpstream) {
QString remote = dlg.remote();
QString branch = dlg.branch();
pushSetUpstream(remote, branch);
}
return true;
}
return false;
}
void BasicMainWindow::push()
{
if (!isRemoteOnline()) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (g->getRemotes().isEmpty()) {
QMessageBox::warning(this, qApp->applicationName(), tr("No remote repository is registered."));
execRepositoryPropertyDialog(QString(), true);
return;
}
int exitcode = 0;
QString errormsg;
reopenRepository(true, [&](GitPtr g){
g->push(false, getPtyProcess());
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
exitcode = getPtyProcess()->getExitCode();
errormsg = getPtyProcess()->getMessage();
});
if (exitcode == 128) {
if (errormsg.indexOf("no upstream branch") >= 0) {
QString brname = currentBranchName();
QString msg = tr("The current branch %1 has no upstream branch.");
msg = msg.arg(brname);
msg += '\n';
msg += tr("You try push --set-upstream");
QMessageBox::warning(this, qApp->applicationName(), msg);
pushSetUpstream(false);
return;
}
if (errormsg.indexOf("Connection refused") >= 0) {
QMessageBox::critical(this, qApp->applicationName(), tr("Connection refused."));
return;
}
}
}
#ifdef Q_OS_MAC
namespace {
bool isValidDir(QString const &dir)
{
if (dir.indexOf('\"') >= 0 || dir.indexOf('\\') >= 0) return false;
return QFileInfo(dir).isDir();
}
}
#include <QProcess>
#endif
-void BasicMainWindow::openTerminal(const RepositoryItem *repo)
+void BasicMainWindow::openTerminal(RepositoryItem const *repo)
{
runOnRepositoryDir([](QString dir){
#ifdef Q_OS_MAC
if (!isValidDir(dir)) return;
QString cmd = "open -n -a /Applications/Utilities/Terminal.app --args \"%1\"";
cmd = cmd.arg(dir);
QProcess::execute(cmd);
#else
Terminal::open(dir);
#endif
}, repo);
}
-void BasicMainWindow::openExplorer(const RepositoryItem *repo)
+void BasicMainWindow::openExplorer(RepositoryItem const *repo)
{
runOnRepositoryDir([](QString dir){
#ifdef Q_OS_MAC
if (!isValidDir(dir)) return;
QString cmd = "open \"%1\"";
cmd = cmd.arg(dir);
QProcess::execute(cmd);
#else
QDesktopServices::openUrl(dir);
#endif
}, repo);
}
-const Git::CommitItem *BasicMainWindow::selectedCommitItem() const
+Git::CommitItem const *BasicMainWindow::selectedCommitItem() const
{
int i = selectedLogIndex();
return commitItem(i);
}
-void BasicMainWindow::deleteBranch(const Git::CommitItem *commit)
+void BasicMainWindow::deleteBranch(Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QStringList all_local_branch_names;
QStringList current_local_branch_names;
{
NamedCommitList named_commits = namedCommitItems(Branches);
JumpDialog::sort(&named_commits);
for (NamedCommitItem const &item : named_commits) {
if (item.name == "HEAD") continue;
if (item.id == commit->commit_id) {
current_local_branch_names.push_back(item.name);
}
all_local_branch_names.push_back(item.name);
}
}
DeleteBranchDialog dlg(this, false, all_local_branch_names, current_local_branch_names);
if (dlg.exec() == QDialog::Accepted) {
setLogEnabled(g, true);
QStringList names = dlg.selectedBranchNames();
int count = 0;
for (QString const &name : names) {
if (g->git(QString("branch -D \"%1\"").arg(name))) {
count++;
} else {
writeLog(tr("Failed to delete the branch '%1'\n").arg(name));
}
}
if (count > 0) {
openRepository(true);
}
}
}
void BasicMainWindow::deleteBranch()
{
deleteBranch(selectedCommitItem());
}
-bool BasicMainWindow::askAreYouSureYouWantToRun(const QString &title, const QString &command)
+bool BasicMainWindow::askAreYouSureYouWantToRun(QString const &title, QString const &command)
{
QString message = tr("Are you sure you want to run the following command ?");
QString text = "%1\n\n%2";
text = text.arg(message).arg(command);
return QMessageBox::warning(this, title, text, QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Ok;
}
-bool BasicMainWindow::editFile(const QString &path, const QString &title)
+bool BasicMainWindow::editFile(QString const &path, QString const &title)
{
return TextEditDialog::editFile(this, path, title);
}
-void BasicMainWindow::resetFile(const QStringList &paths)
+void BasicMainWindow::resetFile(QStringList const &paths)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (paths.isEmpty()) {
// nop
} else {
QString cmd = "git checkout -- \"%1\"";
cmd = cmd.arg(paths[0]);
if (askAreYouSureYouWantToRun(tr("Reset a file"), "> " + cmd)) {
for (QString const &path : paths) {
g->resetFile(path);
}
openRepository(true);
}
}
}
void BasicMainWindow::saveRepositoryBookmark(RepositoryItem item)
{
if (item.local_dir.isEmpty()) return;
if (item.name.isEmpty()) {
item.name = tr("Unnamed");
}
auto *repos = getReposPtr();
bool done = false;
for (auto &repo : *repos) {
RepositoryItem *p = &repo;
if (item.local_dir == p->local_dir) {
*p = item;
done = true;
break;
}
}
if (!done) {
repos->push_back(item);
}
saveRepositoryBookmarks();
updateRepositoriesList();
}
-void BasicMainWindow::setCurrentRepository(const RepositoryItem &repo, bool clear_authentication)
+void BasicMainWindow::setCurrentRepository(RepositoryItem const &repo, bool clear_authentication)
{
if (clear_authentication) {
clearAuthentication();
}
m->current_repo = repo;
}
-void BasicMainWindow::internalDeleteTags(const QStringList &tagnames)
+void BasicMainWindow::internalDeleteTags(QStringList const &tagnames)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (!tagnames.isEmpty()) {
reopenRepository(false, [&](GitPtr g){
for (QString const &name : tagnames) {
g->delete_tag(name, true);
}
});
}
}
-bool BasicMainWindow::internalAddTag(const QString &name)
+bool BasicMainWindow::internalAddTag(QString const &name)
{
if (name.isEmpty()) return false;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return false;
QString commit_id;
Git::CommitItem const *commit = selectedCommitItem();
if (commit && !commit->commit_id.isEmpty()) {
commit_id = commit->commit_id;
}
if (!Git::isValidID(commit_id)) return false;
bool ok = false;
reopenRepository(false, [&](GitPtr g){
ok = g->tag(name, commit_id);
});
return ok;
}
NamedCommitList BasicMainWindow::namedCommitItems(int flags)
{
NamedCommitList items;
if (flags & Branches) {
for (auto pair: branchMapRef()) {
QList<Git::Branch> const &list = pair.second;
for (Git::Branch const &b : list) {
if (b.isHeadDetached()) continue;
QString key = b.name;
if (flags & NamedCommitFlag::Remotes) {
if (!b.remote.isEmpty()) {
key = "remotes" / b.remote / key;
}
} else {
if (!b.remote.isEmpty()) continue;
}
NamedCommitItem item;
item.type = NamedCommitItem::Type::Branch;
item.remote = b.remote;
item.name = b.name;
item.id = b.id;
items.push_back(item);
}
}
}
if (flags & Tags) {
for (auto pair: *ptrTagMap()) {
QList<Git::Tag> const &list = pair.second;
for (Git::Tag const &t : list) {
NamedCommitItem item;
item.type = NamedCommitItem::Type::Tag;
item.name = t.name;
item.id = t.id;
items.push_back(item);
}
}
}
return items;
}
-int BasicMainWindow::rowFromCommitId(const QString &id)
+int BasicMainWindow::rowFromCommitId(QString const &id)
{
auto const &logs = getLogs();
for (size_t i = 0; i < logs.size(); i++) {
Git::CommitItem const &item = logs[i];
if (item.commit_id == id) {
return (int)i;
}
}
return -1;
}
-void BasicMainWindow::createRepository(const QString &dir)
+void BasicMainWindow::createRepository(QString const &dir)
{
CreateRepositoryDialog dlg(this, dir);
if (dlg.exec() == QDialog::Accepted) {
QString path = dlg.path();
if (QFileInfo(path).isDir()) {
if (Git::isValidWorkingCopy(path)) {
// A valid git repository already exists there.
} else {
GitPtr g = git(path);
if (g->init()) {
QString name = dlg.name();
if (!name.isEmpty()) {
addWorkingCopyDir(path, name, true);
}
QString remote_name = dlg.remoteName();
QString remote_url = dlg.remoteURL();
if (!remote_name.isEmpty() && !remote_url.isEmpty()) {
g->addRemoteURL(remote_name, remote_url);
}
}
}
} else {
// not dir
}
}
}
void BasicMainWindow::setLogEnabled(GitPtr g, bool f)
{
if (f) {
g->setLogCallback(git_callback, this);
} else {
g->setLogCallback(nullptr, nullptr);
}
}
-QList<Git::Tag> BasicMainWindow::findTag(const QString &id)
+QList<Git::Tag> BasicMainWindow::findTag(QString const &id)
{
auto it = ptrTagMap()->find(id);
if (it != ptrTagMap()->end()) {
return it->second;
}
return QList<Git::Tag>();
}
-void BasicMainWindow::sshSetPassphrase(const std::string &user, const std::string &pass)
+void BasicMainWindow::sshSetPassphrase(std::string const &user, std::string const &pass)
{
m->ssh_passphrase_user = user;
m->ssh_passphrase_pass = pass;
}
std::string BasicMainWindow::sshPassphraseUser() const
{
return m->ssh_passphrase_user;
}
std::string BasicMainWindow::sshPassphrasePass() const
{
return m->ssh_passphrase_pass;
}
-void BasicMainWindow::httpSetAuthentication(const std::string &user, const std::string &pass)
+void BasicMainWindow::httpSetAuthentication(std::string const &user, std::string const &pass)
{
m->http_uid = user;
m->http_pwd = pass;
}
std::string BasicMainWindow::httpAuthenticationUser() const
{
return m->http_uid;
}
std::string BasicMainWindow::httpAuthenticationPass() const
{
return m->http_uid;
}
-
diff --git a/src/BasicMainWindow.h b/src/BasicMainWindow.h
index 5b509af..84c11d1 100644
--- a/src/BasicMainWindow.h
+++ b/src/BasicMainWindow.h
@@ -1,366 +1,369 @@
#ifndef BASICMAINWINDOW_H
#define BASICMAINWINDOW_H
#include "Git.h"
#include "GitObjectManager.h"
#include "MyProcess.h"
#include "RepositoryData.h"
#include "TextEditorTheme.h"
#include <QMainWindow>
#include <functional>
class ApplicationSettings;
class AvatarLoader;
class QListWidget;
class QListWidgetItem;
class QTreeWidgetItem;
class WebContext;
struct GitHubRepositoryInfo {
QString owner_account_name;
QString repository_name;
};
#define PATH_PREFIX "*"
class BasicMainWindow : public QMainWindow {
Q_OBJECT
public:
struct Label {
enum {
Head,
LocalBranch,
RemoteBranch,
Tag,
};
int kind;
QString text;
Label(int kind = LocalBranch)
: kind(kind)
{
}
};
protected:
enum class PtyCondition {
None,
Clone,
Fetch,
Pull,
Push,
};
enum InteractionMode {
None,
Busy,
};
enum NamedCommitFlag {
Branches = 0x0001,
Tags = 0x0002,
Remotes = 0x0100,
};
enum class FilesListType {
SingleList,
SideBySide,
};
struct Task {
int index = 0;
int parent = 0;
Task() = default;
Task(int index, int parent)
: index(index)
, parent(parent)
{
}
};
struct Element {
int depth = 0;
std::vector<int> indexes;
};
enum {
IndexRole = Qt::UserRole,
FilePathRole,
DiffIndexRole,
HunkIndexRole,
};
enum {
GroupItem = -1,
};
struct Private;
Private *m;
-protected:
-private:
private:
static bool git_callback(void *cookie, const char *ptr, int len);
- QString selectCommand_(const QString &cmdname, const QStringList &cmdfiles, const QStringList &list, QString path, std::function<void (const QString &)> const &callback);
- QString selectCommand_(const QString &cmdname, const QString &cmdfile, const QStringList &list, const QString &path, std::function<void (const QString &)> const &callback);
+ QString selectCommand_(QString const &cmdname, QStringList const &cmdfiles, QStringList const &list, QString path, std::function<void (QString const &)> const &callback);
+ QString selectCommand_(QString const &cmdname, QString const &cmdfile, QStringList const &list, QString const &path, std::function<void (QString const &)> const &callback);
bool checkGitCommand();
bool checkFileCommand();
- bool checkExecutable(const QString &path);
- void internalSetCommand(const QString &path, bool save, const QString &name, QString *out);
- QStringList whichCommand_(const QString &cmdfile1, const QString &cmdfile2 = QString());
+ bool checkExecutable(QString const &path);
+ void internalSetCommand(QString const &path, bool save, QString const &name, QString *out);
+ QStringList whichCommand_(QString const &cmdfile1, QString const &cmdfile2 = QString());
void emitWriteLog(QByteArray ba);
- void setWindowTitle_(const Git::User &user);
+ void setWindowTitle_(Git::User const &user);
bool execSetGlobalUserDialog();
- bool saveByteArrayAs(const QByteArray &ba, const QString &dstpath);
- bool saveFileAs(const QString &srcpath, const QString &dstpath);
- bool saveBlobAs(const QString &id, const QString &dstpath);
+ bool saveByteArrayAs(const QByteArray &ba, QString const &dstpath);
+ bool saveFileAs(QString const &srcpath, QString const &dstpath);
+ bool saveBlobAs(QString const &id, QString const &dstpath);
- void updateFilesList(const Git::CommitItem &commit, bool wait);
+ void updateFilesList(Git::CommitItem const &commit, bool wait);
void revertAllFiles();
- void setCurrentRemoteName(const QString &name);
+ void setCurrentRemoteName(QString const &name);
- void deleteTags(const Git::CommitItem &commit);
+ void deleteTags(Git::CommitItem const &commit);
bool isAvatarEnabled() const;
bool isGitHub() const;
QStringList remotes() const;
- QList<Git::Branch> findBranch(const QString &id);
- QString saveAsTemp(const QString &id);
+ QList<Git::Branch> findBranch(QString const &id);
+ QString saveAsTemp(QString const &id);
QString tempfileHeader() const;
void deleteTempFiles();
- QString getCommitIdFromTag(const QString &tag);
- bool isValidRemoteURL(const QString &url);
+ QString getCommitIdFromTag(QString const &tag);
+ bool isValidRemoteURL(QString const &url);
QString getObjectID(QListWidgetItem *item);
void addWorkingCopyDir(QString dir, QString name, bool open);
- static QString makeRepositoryName(const QString &loc);
+ static QString makeRepositoryName(QString const &loc);
void clearAuthentication();
- const RepositoryItem *findRegisteredRepository(QString *workdir) const;
+ RepositoryItem const *findRegisteredRepository(QString *workdir) const;
void queryRemotes(GitPtr g);
- bool runOnRepositoryDir(std::function<void (QString)> callback, const RepositoryItem *repo);
+ bool runOnRepositoryDir(std::function<void (QString)> callback, RepositoryItem const *repo);
void clearSshAuthentication();
protected:
static QString getFilePath(QListWidgetItem *item);
static bool isGroupItem(QTreeWidgetItem *item);
static int indexOfLog(QListWidgetItem *item);
static int indexOfDiff(QListWidgetItem *item);
static int getHunkIndex(QListWidgetItem *item);
void initNetworking();
bool saveRepositoryBookmarks() const;
QString getBookmarksFilePath() const;
void stopPtyProcess();
void abortPtyProcess();
bool execWelcomeWizardDialog();
void execRepositoryPropertyDialog(QString workdir, bool open_repository_menu = false);
- void execSetUserDialog(const Git::User &global_user, const Git::User &repo_user, const QString &reponame);
- void setGitCommand(const QString &path, bool save);
- void setFileCommand(const QString &path, bool save);
- void setGpgCommand(const QString &path, bool save);
+ void execSetUserDialog(Git::User const &global_user, Git::User const &repo_user, QString const &reponame);
+ void setGitCommand(QString const &path, bool save);
+ void setFileCommand(QString const &path, bool save);
+ void setGpgCommand(QString const &path, bool save);
void logGitVersion();
void setUnknownRepositoryInfo();
void internalClearRepositoryInfo();
void checkUser();
void openRepository(bool validate, bool waitcursor = true);
void reopenRepository(bool log, std::function<void (GitPtr)> callback);
void openSelectedRepository();
void checkRemoteUpdate();
bool isThereUncommitedChanges() const;
bool makeDiff(QString id, QList<Git::Diff> *out);
- void addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (const QString &, QString, int)> &add_item);
+ void addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (QString const &, QString, int)> &add_item);
Git::CommitItemList retrieveCommitLog(GitPtr g);
void queryBranches(GitPtr g);
std::map<QString, QList<Git::Branch>> &branchMapRef();
void updateCommitTableLater();
void updateRemoteInfo();
void updateWindowTitle(GitPtr g);
QString makeCommitInfoText(int row, QList<Label> *label_list);
void removeRepositoryFromBookmark(int index, bool ask);
void clone();
void checkout();
void commit(bool amend = false);
void commitAmend();
- void pushSetUpstream(const QString &remote, const QString &branch);
+ void pushSetUpstream(QString const &remote, QString const &branch);
bool pushSetUpstream(bool testonly);
void push();
- void openTerminal(const RepositoryItem *repo);
- void openExplorer(const RepositoryItem *repo);
- const Git::CommitItem *selectedCommitItem() const;
- void deleteBranch(const Git::CommitItem *commit);
+ void openTerminal(RepositoryItem const *repo);
+ void openExplorer(RepositoryItem const *repo);
+ Git::CommitItem const *selectedCommitItem() const;
+ void deleteBranch(Git::CommitItem const *commit);
void deleteBranch();
- bool askAreYouSureYouWantToRun(const QString &title, const QString &command);
- bool editFile(const QString &path, const QString &title);
- void resetFile(const QStringList &paths);
+ bool askAreYouSureYouWantToRun(QString const &title, QString const &command);
+ bool editFile(QString const &path, QString const &title);
+ void resetFile(QStringList const &paths);
void saveRepositoryBookmark(RepositoryItem item);
- void setCurrentRepository(const RepositoryItem &repo, bool clear_authentication);
- void internalDeleteTags(const QStringList &tagnames);
- bool internalAddTag(const QString &name);
+ void setCurrentRepository(RepositoryItem const &repo, bool clear_authentication);
+ void internalDeleteTags(QStringList const &tagnames);
+ bool internalAddTag(QString const &name);
NamedCommitList namedCommitItems(int flags);
- int rowFromCommitId(const QString &id);
- void createRepository(const QString &dir);
+ int rowFromCommitId(QString const &id);
+ void createRepository(QString const &dir);
void setLogEnabled(GitPtr g, bool f);
- QList<Git::Tag> findTag(const QString &id);
- void sshSetPassphrase(const std::string &user, const std::string &pass);
+ QList<Git::Tag> findTag(QString const &id);
+ void sshSetPassphrase(std::string const &user, std::string const &pass);
std::string sshPassphraseUser() const;
std::string sshPassphrasePass() const;
void httpSetAuthentication(std::string const &user, std::string const &pass);
std::string httpAuthenticationUser() const;
std::string httpAuthenticationPass() const;
public:
Git::CommitItemList const &getLogs() const;
- const Git::CommitItem *getLog(int index) const;
+ Git::CommitItem const *getLog(int index) const;
protected:
+ bool event(QEvent *event) override;
+ void postUserFunctionEvent(const std::function<void(QVariant const &)> &fn, QVariant const &v = QVariant());
+
Git::CommitItemList *getLogsPtr();
void setLogs(const Git::CommitItemList &logs);
void clearLogs();
PtyProcess *getPtyProcess();
PtyCondition getPtyCondition();
bool getPtyProcessOk() const;
void setPtyProcessOk(bool pty_process_ok);
+ void setPtyUserData(QVariant const &userdata);
void setPtyCondition(const PtyCondition &ptyCondition);
QList<RepositoryItem> const &getRepos() const;
QList<RepositoryItem> *getReposPtr();
QString getCurrentRemoteName() const;
AvatarLoader *getAvatarLoader();
const AvatarLoader *getAvatarLoader() const;
int *ptrUpdateFilesListCounter();
int *ptrUpdateCommitTableCounter();
bool interactionCanceled() const;
void setInteractionCanceled(bool canceled);
InteractionMode interactionMode() const;
void setInteractionMode(const InteractionMode &im);
QString getRepositoryFilterText() const;
- void setRepositoryFilterText(const QString &text);
+ void setRepositoryFilterText(QString const &text);
void setUncommitedChanges(bool uncommited_changes);
QList<Git::Diff> *diffResult();
std::map<QString, Git::Diff> *getDiffCacheMap();
bool getRemoteChanged() const;
void setRemoteChanged(bool remote_changed);
void setServerType(const ServerType &server_type);
GitHubRepositoryInfo *ptrGitHub();
std::map<int, QList<Label> > *getLabelMap();
void clearLabelMap();
GitObjectCache *getObjCache();
bool getForceFetch() const;
void setForceFetch(bool force_fetch);
std::map<QString, QList<Git::Tag>> *ptrTagMap();
QString getHeadId() const;
- void setHeadId(const QString &head_id);
+ void setHeadId(QString const &head_id);
- RepositoryItem const &getTempRepoForCloneComplete() const;
+ void setPtyProcessCompletionData(QVariant const &value);
+ QVariant const &getTempRepoForCloneCompleteV() const;
protected:
virtual void setCurrentLogRow(int row) = 0;
virtual void updateFilesList(QString id, bool wait) = 0;
- virtual void setRepositoryInfo(const QString &reponame, const QString &brname) = 0;
- virtual void deleteTags(const QStringList &tagnames) = 0;
+ virtual void setRepositoryInfo(QString const &reponame, QString const &brname) = 0;
+ virtual void deleteTags(QStringList const &tagnames) = 0;
virtual void internalWriteLog(const char *ptr, int len) = 0;
virtual void removeSelectedRepositoryFromBookmark(bool ask) = 0;
virtual void openRepository_(GitPtr g) = 0;
virtual void updateRepositoriesList() = 0;
virtual int selectedLogIndex() const = 0;
virtual void clearFileList() = 0;
- virtual const RepositoryItem *selectedRepositoryItem() const = 0;
+ virtual RepositoryItem const *selectedRepositoryItem() const = 0;
virtual bool isRemoteOnline() const = 0;
public:
explicit BasicMainWindow(QWidget *parent = nullptr);
~BasicMainWindow();
ApplicationSettings *appsettings();
const ApplicationSettings *appsettings() const;
WebContext *webContext();
QString gitCommand() const;
void autoOpenRepository(QString dir);
- GitPtr git(const QString &dir) const;
+ GitPtr git(QString const &dir) const;
GitPtr git();
QPixmap getTransparentPixmap();
QIcon committerIcon(int row) const;
- const Git::CommitItem *commitItem(int row) const;
+ Git::CommitItem const *commitItem(int row) const;
QIcon verifiedIcon(char s) const;
virtual QString currentWorkingCopyDir() const;
- const QList<BasicMainWindow::Label> *label(int row);
- bool saveAs(const QString &id, const QString &dstpath);
- bool testRemoteRepositoryValidity(const QString &url);
+ QList<BasicMainWindow::Label> const *label(int row);
+ bool saveAs(QString const &id, QString const &dstpath);
+ bool testRemoteRepositoryValidity(QString const &url);
QString defaultWorkingDir() const;
void addWorkingCopyDir(QString dir, bool open);
- bool queryCommit(const QString &id, Git::CommitItem *out);
+ bool queryCommit(QString const &id, Git::CommitItem *out);
QAction *addMenuActionProperty(QMenu *menu);
- void checkout(QWidget *parent, const Git::CommitItem *commit);
+ void checkout(QWidget *parent, Git::CommitItem const *commit);
void jumpToCommit(QString id);
- void execCommitViewWindow(const Git::CommitItem *commit);
- void execCommitExploreWindow(QWidget *parent, const Git::CommitItem *commit);
- void execCommitPropertyDialog(QWidget *parent, const Git::CommitItem *commit);
- void execFileHistory(const QString &path);
+ void execCommitViewWindow(Git::CommitItem const *commit);
+ void execCommitExploreWindow(QWidget *parent, Git::CommitItem const *commit);
+ void execCommitPropertyDialog(QWidget *parent, Git::CommitItem const *commit);
+ void execFileHistory(QString const &path);
void execFileHistory(QListWidgetItem *item);
void execFilePropertyDialog(QListWidgetItem *item);
QString selectGitCommand(bool save);
QString selectFileCommand(bool save);
QString selectGpgCommand(bool save);
- const Git::Branch &currentBranch() const;
- void setCurrentBranch(const Git::Branch &b);
+ Git::Branch const &currentBranch() const;
+ void setCurrentBranch(Git::Branch const &b);
QString currentRepositoryName() const;
QString currentRemoteName() const;
QString currentBranchName() const;
bool isValidWorkingCopy(const GitPtr &g) const;
- QString determinFileType_(const QString &path, bool mime, std::function<void (const QString &, QByteArray *)> callback) const;
- QString determinFileType(const QString &path, bool mime);
+ QString determinFileType_(QString const &path, bool mime, std::function<void (QString const &, QByteArray *)> callback) const;
+ QString determinFileType(QString const &path, bool mime);
QString determinFileType(QByteArray in, bool mime);
QList<Git::Tag> queryTagList();
int limitLogCount() const;
TextEditorThemePtr themeForTextEditor();
- Git::Object cat_file_(GitPtr g, const QString &id);
- Git::Object cat_file(const QString &id);
+ Git::Object cat_file_(GitPtr g, QString const &id);
+ Git::Object cat_file(QString const &id);
QString newTempFilePath();
- QString findFileID(GitPtr, const QString &commit_id, const QString &file);
- void updateFilesList(const QString &id, QList<Git::Diff> *diff_list, QListWidget *listwidget);
+ QString findFileID(GitPtr, QString const &commit_id, QString const &file);
+ void updateFilesList(QString const &id, QList<Git::Diff> *diff_list, QListWidget *listwidget);
void setAppSettings(const ApplicationSettings &appsettings);
QIcon getRepositoryIcon() const;
QIcon getFolderIcon() const;
QIcon getSignatureGoodIcon() const;
QIcon getSignatureDubiousIcon() const;
QIcon getSignatureBadIcon() const;
QPixmap getTransparentPixmap() const;
- static QString abbrevCommitID(const Git::CommitItem &commit);
+ static QString abbrevCommitID(Git::CommitItem const &commit);
protected slots:
void onAvatarUpdated();
public slots:
void writeLog(const char *ptr, int len);
- void writeLog(const QString &str);
+ void writeLog(QString const &str);
void writeLog(QByteArray ba);
signals:
void signalWriteLog(QByteArray ba);
void remoteInfoChanged();
void signalCheckRemoteUpdate();
};
#endif // BASICMAINWINDOW_H
diff --git a/src/CommitDialog.cpp b/src/CommitDialog.cpp
index 0fc1e03..db28c75 100644
--- a/src/CommitDialog.cpp
+++ b/src/CommitDialog.cpp
@@ -1,95 +1,95 @@
#include "CommitDialog.h"
#include "ui_CommitDialog.h"
#include "BasicMainWindow.h"
#include "ConfigSigningDialog.h"
#include <QDir>
-CommitDialog::CommitDialog(BasicMainWindow *parent, QString const &reponame, const Git::User &user, const gpg::Data &key) :
+CommitDialog::CommitDialog(BasicMainWindow *parent, QString const &reponame, Git::User const &user, gpg::Data const &key) :
QDialog(parent),
ui(new Ui::CommitDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
key_ = key;
ui->label_reponame->setText(reponame);
ui->label_commit_author->setText(user.name);
ui->label_commit_mail->setText(user.email);
updateSigningInfo();
ui->plainTextEdit->setFocus();
}
CommitDialog::~CommitDialog()
{
delete ui;
}
BasicMainWindow *CommitDialog::mainwindow()
{
return qobject_cast<BasicMainWindow *>(parent());
}
void CommitDialog::updateSigningInfo()
{
GitPtr g = mainwindow()->git();
Git::SignPolicy pol = g->signPolicy(Git::Source::Default);
if (!key_.id.isEmpty()) {
if (pol == Git::SignPolicy::True) {
ui->groupBox_gpg_sign->setCheckable(false);
} else {
ui->groupBox_gpg_sign->setCheckable(true);
ui->groupBox_gpg_sign->setChecked(false);
}
ui->label_sign_id->setText(key_.id);
ui->label_sign_name->setText(key_.name);
ui->label_sign_mail->setText(key_.mail);
} else {
ui->groupBox_gpg_sign->setChecked(false);
ui->groupBox_gpg_sign->setEnabled(false);
}
}
bool CommitDialog::isSigningEnabled() const
{
return ui->groupBox_gpg_sign->isChecked();
}
void CommitDialog::setText(QString const &text)
{
ui->plainTextEdit->setPlainText(text);
QTextCursor cur = ui->plainTextEdit->textCursor();
cur.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
ui->plainTextEdit->setTextCursor(cur);
}
QString CommitDialog::text() const
{
return ui->plainTextEdit->toPlainText();
}
void CommitDialog::keyPressEvent(QKeyEvent *event)
{
if (event->modifiers() & Qt::ControlModifier) {
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
event->accept();
accept();
return;
}
}
QDialog::keyPressEvent(event);
}
void CommitDialog::on_pushButton_config_signing_clicked()
{
ConfigSigningDialog dlg(this, mainwindow(), true);
if (dlg.exec() == QDialog::Accepted) {
updateSigningInfo();
}
}
diff --git a/src/CommitExploreWindow.cpp b/src/CommitExploreWindow.cpp
index 0f28297..1b1ef95 100644
--- a/src/CommitExploreWindow.cpp
+++ b/src/CommitExploreWindow.cpp
@@ -1,279 +1,279 @@
#include "CommitExploreWindow.h"
#include "ui_CommitExploreWindow.h"
#include "GitObjectManager.h"
#include "ImageViewWidget.h"
#include "MainWindow.h"
#include "common/misc.h"
#include "main.h"
#include "platform.h"
#include <QFileIconProvider>
#include <QMenu>
#include <memory>
static QTreeWidgetItem *newQTreeWidgetItem()
{
auto *item = new QTreeWidgetItem;
item->setSizeHint(0, QSize(20, 20));
return item;
}
enum {
ItemTypeRole = Qt::UserRole,
ObjectIdRole,
FilePathRole,
};
struct CommitExploreWindow::Private {
BasicMainWindow *mainwindow;
GitObjectCache *objcache;
Git::CommitItem const *commit;
QString root_tree_id;
GitTreeItemList tree_item_list;
Git::Object content_object;
ObjectContent content;
TextEditorEnginePtr text_editor_engine;
};
-CommitExploreWindow::CommitExploreWindow(QWidget *parent, BasicMainWindow *mainwin, GitObjectCache *objcache, const Git::CommitItem *commit)
+CommitExploreWindow::CommitExploreWindow(QWidget *parent, BasicMainWindow *mainwin, GitObjectCache *objcache, Git::CommitItem const *commit)
: QDialog(parent)
, ui(new Ui::CommitExploreWindow)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
flags |= Qt::WindowMaximizeButtonHint;
setWindowFlags(flags);
m->mainwindow = mainwin;
m->objcache = objcache;
m->commit = commit;
m->text_editor_engine = std::make_shared<TextEditorEngine>();
ui->widget_fileview->bind(mainwin, nullptr, ui->verticalScrollBar, ui->horizontalScrollBar, mainwin->themeForTextEditor());
ui->widget_fileview->setDiffMode(m->text_editor_engine, ui->verticalScrollBar, ui->horizontalScrollBar);
ui->splitter->setSizes({100, 100, 200});
// set text
ui->lineEdit_commit_id->setText(commit->commit_id);
ui->lineEdit_date->setText(misc::makeDateTimeString(commit->commit_date));
ui->lineEdit_author->setText(commit->author);
{
GitCommit c;
c.parseCommit(objcache, m->commit->commit_id);
m->root_tree_id = c.tree_id;
}
{
GitCommitTree tree(objcache);
tree.parseTree(m->root_tree_id);
}
{
QTreeWidgetItem *rootitem = newQTreeWidgetItem();
rootitem->setText(0, tr("Commit"));
rootitem->setData(0, ItemTypeRole, (int)GitTreeItem::TREE);
rootitem->setData(0, ObjectIdRole, m->root_tree_id);
ui->treeWidget->addTopLevelItem(rootitem);
loadTree(m->root_tree_id);
rootitem->setExpanded(true);
}
}
CommitExploreWindow::~CommitExploreWindow()
{
delete m;
delete ui;
}
BasicMainWindow *CommitExploreWindow::mainwindow()
{
return m->mainwindow;
}
void CommitExploreWindow::clearContent()
{
m->content = ObjectContent();
}
void CommitExploreWindow::expandTreeItem_(QTreeWidgetItem *item)
{
if (item->childCount() == 1) {
if (item->child(0)->text(0).isEmpty()) {
delete item->takeChild(0);
}
}
if (item->childCount() == 0) {
QFileIconProvider icons;
QString path = item->data(0, FilePathRole).toString();
QString tree_id = item->data(0, ObjectIdRole).toString();
loadTree(tree_id);
for (GitTreeItem const &ti : m->tree_item_list) {
if (ti.type == GitTreeItem::TREE) {
QTreeWidgetItem *child = newQTreeWidgetItem();
child->setIcon(0, icons.icon(QFileIconProvider::Folder));
child->setText(0, ti.name);
child->setData(0, ItemTypeRole, (int)ti.type);
child->setData(0, ObjectIdRole, ti.id);
child->setData(0, FilePathRole, misc::joinWithSlash(path, ti.name));
QTreeWidgetItem *placeholder = newQTreeWidgetItem();
child->addChild(placeholder);
item->addChild(child);
}
}
}
}
void CommitExploreWindow::on_treeWidget_itemExpanded(QTreeWidgetItem *item)
{
expandTreeItem_(item);
}
void CommitExploreWindow::loadTree(QString const &tree_id)
{
GitCommitTree tree(m->objcache);
tree.parseTree(tree_id);
m->tree_item_list = *tree.treelist();
std::sort(m->tree_item_list.begin(), m->tree_item_list.end(), [](GitTreeItem const &left, GitTreeItem const &right){
int l = (left.type == GitTreeItem::TREE) ? 0 : 1;
int r = (right.type == GitTreeItem::TREE) ? 0 : 1;
if (l != r) return l < r;
return left.name.compare(right.name, Qt::CaseInsensitive) < 0;
});
}
void CommitExploreWindow::doTreeItemChanged_(QTreeWidgetItem *current)
{
ui->listWidget->clear();
QString path = current->data(0, FilePathRole).toString();
QString tree_id = current->data(0, ObjectIdRole).toString();
loadTree(tree_id);
QFileIconProvider icons;
for (GitTreeItem const &ti : m->tree_item_list) {
QIcon icon;
if (ti.type == GitTreeItem::TREE) {
icon = icons.icon(QFileIconProvider::Folder);
} else {
#ifdef Q_OS_WIN
{
int i = ti.name.lastIndexOf('.');
if (i > 0) {
QString ext = ti.name.mid(i + 1);
icon = winIconFromExtensionLarge(ext);
}
}
#endif
if (icon.isNull()) {
icon = icons.icon(QFileIconProvider::File);
}
}
auto *p = new QListWidgetItem;
p->setIcon(icon);
p->setText(ti.name);
p->setData(ItemTypeRole, (int)ti.type);
p->setData(ObjectIdRole, ti.id);
p->setData(FilePathRole, misc::joinWithSlash(path, ti.name));
ui->listWidget->addItem(p);
}
}
void CommitExploreWindow::on_treeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem * /*previous*/)
{
clearContent();
doTreeItemChanged_(current);
}
void CommitExploreWindow::on_listWidget_itemDoubleClicked(QListWidgetItem *item)
{
Q_ASSERT(item);
GitTreeItem::Type type = (GitTreeItem::Type)item->data(ItemTypeRole).toInt();
if (type == GitTreeItem::TREE) {
QString tree_id = item->data(ObjectIdRole).toString();
clearContent();
QTreeWidgetItem *parent = ui->treeWidget->currentItem();
expandTreeItem_(parent);
parent->setExpanded(true);
int n = parent->childCount();
for (int i = 0; i < n; i++) {
QTreeWidgetItem *child = parent->child(i);
if (child->data(0, ItemTypeRole).toInt() == GitTreeItem::TREE) {
QString tid = child->data(0, ObjectIdRole).toString();
if (tid == tree_id) {
child->setExpanded(true);
ui->treeWidget->setCurrentItem(child);
break;
}
}
}
}
}
void CommitExploreWindow::on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem * /*previous*/)
{
if (!current) return;
GitTreeItem::Type type = (GitTreeItem::Type)current->data(ItemTypeRole).toInt();
if (type == GitTreeItem::BLOB) {
QString id = current->data(ObjectIdRole).toString();
m->content_object = m->objcache->catFile(id);
QString path = current->data(FilePathRole).toString();
clearContent();
QString mimetype = mainwindow()->determinFileType(m->content_object.content, true);
if (misc::isImage(mimetype)) {
ui->widget_fileview->setImage(mimetype, m->content_object.content, id, path);
} else {
ui->widget_fileview->setText(m->content_object.content, mainwindow(), id, path);
}
} else {
clearContent();
}
}
void CommitExploreWindow::on_verticalScrollBar_valueChanged(int)
{
ui->widget_fileview->refrectScrollBar();
}
void CommitExploreWindow::on_horizontalScrollBar_valueChanged(int)
{
ui->widget_fileview->refrectScrollBar();
}
void CommitExploreWindow::on_listWidget_customContextMenuRequested(const QPoint &pos)
{
(void)pos;
QListWidgetItem *current = ui->listWidget->currentItem();
if (!current) return;
GitTreeItem::Type type = (GitTreeItem::Type)current->data(ItemTypeRole).toInt();
if (type == GitTreeItem::BLOB) {
QMenu menu;
QAction *a_history = menu.addAction("History");
QAction *a = menu.exec(QCursor::pos() + QPoint(8, -8));
if (a) {
if (a == a_history) {
QString path = current->data(FilePathRole).toString();
mainwindow()->execFileHistory(path);
return;
}
}
}
}
diff --git a/src/CommitViewWindow.cpp b/src/CommitViewWindow.cpp
index bed5d30..f7cdea4 100644
--- a/src/CommitViewWindow.cpp
+++ b/src/CommitViewWindow.cpp
@@ -1,71 +1,71 @@
#include "CommitViewWindow.h"
#include "ui_CommitViewWindow.h"
#include <QMenu>
struct CommitViewWindow::Private {
Git::CommitItem const *commit = nullptr;
QList<Git::Diff> diff_list;
};
-CommitViewWindow::CommitViewWindow(BasicMainWindow *parent, const Git::CommitItem *commit)
+CommitViewWindow::CommitViewWindow(BasicMainWindow *parent, Git::CommitItem const *commit)
: QDialog(parent)
, ui(new Ui::CommitViewWindow)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
m->commit = commit;
ui->widget_diff->bind(mainwindow());
ui->lineEdit_message->setText(m->commit->message);
ui->lineEdit_id->setText(m->commit->commit_id);
mainwindow()->updateFilesList(m->commit->commit_id, &m->diff_list, ui->listWidget_files);
ui->listWidget_files->setCurrentRow(0);
}
CommitViewWindow::~CommitViewWindow()
{
delete m;
delete ui;
}
BasicMainWindow *CommitViewWindow::mainwindow()
{
return qobject_cast<BasicMainWindow *>(parent());
}
void CommitViewWindow::on_listWidget_files_currentRowChanged(int currentRow)
{
if (currentRow >= 0 && currentRow < m->diff_list.size()) {
Git::Diff const &diff = m->diff_list[currentRow];
ui->widget_diff->updateDiffView(diff, false);
}
}
void CommitViewWindow::on_listWidget_files_customContextMenuRequested(const QPoint &pos)
{
GitPtr g = mainwindow()->git();
if (!mainwindow()->isValidWorkingCopy(g)) return;
QMenu menu;
QAction *a_history = menu.addAction(tr("History"));
QAction *a_properties = mainwindow()->addMenuActionProperty(&menu);
QPoint pt = ui->listWidget_files->mapToGlobal(pos) + QPoint(8, -8);
QAction *a = menu.exec(pt);
if (a) {
QListWidgetItem *item = ui->listWidget_files->currentItem();
if (a == a_history) {
mainwindow()->execFileHistory(item);
} else if (a == a_properties) {
mainwindow()->execFilePropertyDialog(item);
}
}
}
diff --git a/src/EditGitIgnoreDialog.h b/src/EditGitIgnoreDialog.h
index 7404699..c93cd9d 100644
--- a/src/EditGitIgnoreDialog.h
+++ b/src/EditGitIgnoreDialog.h
@@ -1,30 +1,30 @@
#ifndef EDITGITIGNOREDIALOG_H
#define EDITGITIGNOREDIALOG_H
#include <QDialog>
class MainWindow;
namespace Ui {
class EditGitIgnoreDialog;
}
class EditGitIgnoreDialog : public QDialog {
Q_OBJECT
private:
QString gitignore_path;
public:
- explicit EditGitIgnoreDialog(MainWindow *parent, const QString &gitignore_path, QString const &file);
+ explicit EditGitIgnoreDialog(MainWindow *parent, QString const &gitignore_path, QString const &file);
~EditGitIgnoreDialog() override;
QString text() const;
private slots:
void on_pushButton_edit_file_clicked();
private:
Ui::EditGitIgnoreDialog *ui;
MainWindow *mainwindow();
};
#endif // EDITGITIGNOREDIALOG_H
diff --git a/src/EditTagsDialog.cpp b/src/EditTagsDialog.cpp
index c42c1f0..c2bf8e5 100644
--- a/src/EditTagsDialog.cpp
+++ b/src/EditTagsDialog.cpp
@@ -1,78 +1,78 @@
#include "InputNewTagDialog.h"
#include "MainWindow.h"
#include "EditTagsDialog.h"
#include "ui_EditTagsDialog.h"
-EditTagsDialog::EditTagsDialog(MainWindow *parent, const Git::CommitItem *commit) :
+EditTagsDialog::EditTagsDialog(MainWindow *parent, Git::CommitItem const *commit) :
QDialog(parent),
ui(new Ui::EditTagsDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
commit_ = commit;
ui->lineEdit_commit_id->setText(commit_->commit_id);
ui->lineEdit_message->setText(commit_->message);
updateTagList();
}
EditTagsDialog::~EditTagsDialog()
{
delete ui;
}
MainWindow *EditTagsDialog::mainwindow()
{
return qobject_cast<MainWindow *>(parent());
}
QList<Git::Tag> EditTagsDialog::queryTagList()
{
return mainwindow()->queryTagList();
}
void EditTagsDialog::updateTagList()
{
ui->listWidget->clear();
QList<Git::Tag> list = queryTagList();
for (Git::Tag const &t : list) {
auto item = new QListWidgetItem(t.name);
ui->listWidget->addItem(item);
}
ui->listWidget->sortItems();
}
QStringList EditTagsDialog::selectedTags()
{
QStringList list;
int n = ui->listWidget->count();
for (int i = 0; i < n; i++) {
QListWidgetItem *item = ui->listWidget->item(i);
if (item->isSelected()) {
list.push_back(item->text());
}
}
return list;
}
void EditTagsDialog::on_pushButton_add_clicked()
{
InputNewTagDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
QString text = dlg.text();
mainwindow()->addTag(text);
updateTagList();
}
}
void EditTagsDialog::on_pushButton_delete_clicked()
{
QStringList list = selectedTags();
mainwindow()->deleteTags(list);
updateTagList();
}
diff --git a/src/FileDiffSliderWidget.cpp b/src/FileDiffSliderWidget.cpp
index b3c5192..d547857 100644
--- a/src/FileDiffSliderWidget.cpp
+++ b/src/FileDiffSliderWidget.cpp
@@ -1,208 +1,208 @@
#include "FileDiffSliderWidget.h"
#include "common/misc.h"
#include <QPainter>
#include <QMouseEvent>
#include <QDebug>
#include "ApplicationGlobal.h"
#include "TextEditorTheme.h"
struct FileDiffSliderWidget::Private {
bool visible = false;
int scroll_total = 0;
int scroll_value = 0;
int scroll_page_size = 0;
QPixmap left_pixmap;
QPixmap right_pixmap;
int wheel_delta = 0;
fn_pixmap_maker_t pixmap_maker;
ThemePtr theme;
};
FileDiffSliderWidget::FileDiffSliderWidget(QWidget *parent)
: QWidget(parent)
, m(new Private)
{
}
FileDiffSliderWidget::~FileDiffSliderWidget()
{
delete m;
}
void FileDiffSliderWidget::init(fn_pixmap_maker_t pixmap_maker, ThemePtr theme)
{
m->pixmap_maker = pixmap_maker;
m->theme = theme;
}
QPixmap FileDiffSliderWidget::makeDiffPixmap(DiffPane pane, int width, int height)
{
if (m->pixmap_maker) {
return m->pixmap_maker(pane, width, height);
}
return QPixmap();
}
void FileDiffSliderWidget::updatePixmap()
{
m->left_pixmap = makeDiffPixmap(DiffPane::Left, 1, height());
m->right_pixmap = makeDiffPixmap(DiffPane::Right, 1, height());
}
-QPixmap FileDiffSliderWidget::makeDiffPixmap(int width, int height, const TextDiffLineList &lines, ThemePtr theme)
+QPixmap FileDiffSliderWidget::makeDiffPixmap(int width, int height, TextDiffLineList const &lines, ThemePtr theme)
{
auto MakePixmap = [&](TextDiffLineList const &lines, int w, int h){
const int scale = 1;
QPixmap pixmap = QPixmap(w, h * scale);
pixmap.fill(theme->diff_slider_normal_bg);
QPainter pr(&pixmap);
auto Loop = [&](std::function<QColor(TextDiffLine::Type)> getcolor){
int i = 0;
while (i < lines.size()) {
auto type = (TextDiffLine::Type)lines[i].type;
int j = i + 1;
if (type != TextDiffLine::Normal) {
while (j < lines.size()) {
if (lines[j].type != type) break;
j++;
}
int y = i * pixmap.height() / lines.size();
int z = j * pixmap.height() / lines.size();
if (z == y) z = y + 1;
QColor color = getcolor(type);
if (color.isValid()) pr.fillRect(0, y, w, z - y, color);
}
i = j;
}
};
Loop([&](TextDiffLine::Type t)->QColor{
switch (t) {
case TextDiffLine::Unknown: return theme->diff_slider_unknown_bg;
}
return QColor();
});
Loop([&](TextDiffLine::Type t)->QColor{
switch (t) {
case TextDiffLine::Add: return theme->diff_slider_add_bg;
case TextDiffLine::Del: return theme->diff_slider_del_bg;
}
return QColor();
});
if (scale == 1) return pixmap;
return pixmap.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
};
return MakePixmap(lines, width, height);
}
void FileDiffSliderWidget::paintEvent(QPaintEvent *)
{
if (!m->visible) return;
if (m->scroll_total < 1) return;
if (m->left_pixmap.isNull() || m->right_pixmap.isNull()) {
updatePixmap();
}
QPainter pr(this);
int w = (width() - 4) / 2;
{
int h = height();
pr.fillRect(w, 0, 4, height(), QColor(240, 240, 240));
int sw = m->left_pixmap.width();
int sh = m->left_pixmap.height();
pr.drawPixmap(0, 0, w, h, m->left_pixmap, 0, 0, sw, sh);
pr.drawPixmap(w + 4, 0, w, h, m->right_pixmap, 0, 0, sw, sh);
}
pr.fillRect(w, 0, 4, height(), palette().color(QPalette::Window));
if (m->scroll_page_size > 0 && m->scroll_total > 0) {
int y = m->scroll_value * height() / m->scroll_total;
int h = m->scroll_page_size * height() / m->scroll_total;
if (h < 2) h = 2;
pr.fillRect(w + 1, y, 2, h, m->theme->diff_slider_handle);
}
if (hasFocus()) {
QPainter pr(this);
misc::drawFrame(&pr, 0, 0, width(), height(), QColor(0, 128, 255, 128));
}
}
void FileDiffSliderWidget::resizeEvent(QResizeEvent *)
{
clear(m->visible);
}
void FileDiffSliderWidget::internalSetValue(int v)
{
int max = m->scroll_total - m->scroll_page_size / 2;
if (v > max) {
v = max;
}
if (v < 0) {
v = 0;
}
m->scroll_value = v;
}
void FileDiffSliderWidget::setValue(int v)
{
internalSetValue(v);
update();
emit valueChanged(m->scroll_value);
}
void FileDiffSliderWidget::scroll_(int pos)
{
int v = pos;
v = v * m->scroll_total / height() - m->scroll_page_size / 2;
setValue(v);
}
void FileDiffSliderWidget::mousePressEvent(QMouseEvent *e)
{
if (m->visible && e->button() == Qt::LeftButton) {
scroll_(e->pos().y());
}
}
void FileDiffSliderWidget::mouseMoveEvent(QMouseEvent *e)
{
if (m->visible && (e->buttons() & Qt::LeftButton)) {
scroll_(e->pos().y());
}
}
void FileDiffSliderWidget::wheelEvent(QWheelEvent *e)
{
int pos = 0;
m->wheel_delta += e->delta();
while (m->wheel_delta >= 40) {
m->wheel_delta -= 40;
pos--;
}
while (m->wheel_delta <= -40) {
m->wheel_delta += 40;
pos++;
}
if (pos != 0) {
setValue(m->scroll_value + pos);
}
}
void FileDiffSliderWidget::clear(bool v)
{
m->left_pixmap = QPixmap();
m->right_pixmap = QPixmap();
m->visible = v;
update();
}
void FileDiffSliderWidget::setScrollPos(int total, int value, int size)
{
m->scroll_total = total;
m->scroll_page_size = size;
internalSetValue(value);
m->visible = (m->scroll_total > 0) && (m->scroll_page_size > 0);
update();
}
diff --git a/src/FileDiffSliderWidget.h b/src/FileDiffSliderWidget.h
index f6ffd80..ef2c177 100644
--- a/src/FileDiffSliderWidget.h
+++ b/src/FileDiffSliderWidget.h
@@ -1,53 +1,53 @@
#ifndef FILEDIFFSLIDERWIDGET_H
#define FILEDIFFSLIDERWIDGET_H
#include "MainWindow.h"
#include <QWidget>
#include <QPixmap>
#include <functional>
#include "AbstractCharacterBasedApplication.h"
#include "Theme.h"
using TextDiffLine = Document::Line;
using TextDiffLineList = QList<Document::Line>;
enum class DiffPane {
Left,
Right,
};
typedef std::function<QPixmap(DiffPane pane, int width, int height)> fn_pixmap_maker_t;
class FileDiffSliderWidget : public QWidget {
Q_OBJECT
private:
struct Private;
Private *m;
void scroll_(int pos);
QPixmap makeDiffPixmap(DiffPane pane, int width, int height);
void setValue(int v);
void internalSetValue(int v);
public:
explicit FileDiffSliderWidget(QWidget *parent = nullptr);
~FileDiffSliderWidget() override;
void clear(bool v);
void setScrollPos(int total, int value, int size);
void init(fn_pixmap_maker_t pixmap_maker, ThemePtr theme);
void updatePixmap();
- static QPixmap makeDiffPixmap(int width, int height, const TextDiffLineList &lines, ThemePtr theme);
+ static QPixmap makeDiffPixmap(int width, int height, TextDiffLineList const &lines, ThemePtr theme);
protected:
void paintEvent(QPaintEvent *) override;
void resizeEvent(QResizeEvent *) override;
void mousePressEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
void wheelEvent(QWheelEvent *event) override;
signals:
void valueChanged(int value);
void scrollByWheel(int lines);
};
#endif // FILEDIFFSLIDERWIDGET_H
diff --git a/src/FileDiffWidget.h b/src/FileDiffWidget.h
index 6695ab9..5701a1c 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, const TextDiffLineList &left, const TextDiffLineList &right);
+ 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);
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/FileUtil.cpp b/src/FileUtil.cpp
index f452c42..66995a9 100644
--- a/src/FileUtil.cpp
+++ b/src/FileUtil.cpp
@@ -1,274 +1,274 @@
#include "FileUtil.h"
#include "common/joinpath.h"
#include <sys/stat.h>
#ifdef __HAIKU__
struct stat s;
#endif
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
std::string FileUtil::getcwd()
{
std::string cwd;
char *p = _getcwd(nullptr, 0);
cwd = p;
free(p);
return cwd;
}
bool FileUtil::isdir(std::string const &path)
{
DWORD f = GetFileAttributesA(path.c_str());
return f != INVALID_FILE_ATTRIBUTES && (f & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
-void FileUtil::mkdir(const std::string &dir, int /*perm*/)
+void FileUtil::mkdir(std::string const &dir, int /*perm*/)
{
CreateDirectoryA(dir.c_str(), nullptr);
}
-void FileUtil::rmdir(const std::string &dir)
+void FileUtil::rmdir(std::string const &dir)
{
RemoveDirectoryA(dir.c_str());
}
-bool FileUtil::chdir(const std::string &dir)
+bool FileUtil::chdir(std::string const &dir)
{
return SetCurrentDirectoryA(dir.c_str()) != 0;
}
-void FileUtil::rmfile(const std::string &path)
+void FileUtil::rmfile(std::string const &path)
{
DeleteFileA(path.c_str());
}
-void FileUtil::mv(const std::string &src, const std::string &dst)
+void FileUtil::mv(std::string const &src, std::string const &dst)
{
MoveFileA(src.c_str(), dst.c_str());
// DWORD e = GetLastError();
}
-void FileUtil::getdirents(const std::string &loc, std::vector<DirEnt> *out)
+void FileUtil::getdirents(std::string const &loc, std::vector<DirEnt> *out)
{
out->clear();
std::string filter = loc / "*.*";
WIN32_FIND_DATAA fd;
HANDLE h = FindFirstFileA(filter.c_str(), &fd);
if (h != INVALID_HANDLE_VALUE) {
do {
DirEnt de;
de.name = fd.cFileName;
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (de.name == "." || de.name == "..") {
continue;
}
de.isdir = true;
}
out->push_back(de);
} while (FindNextFileA(h, &fd));
FindClose(h);
}
}
#else // _WIN32
#include <unistd.h>
#include <dirent.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
std::string FileUtil::getcwd()
{
std::string cwd;
char *p = ::getcwd(nullptr, 0);
cwd = p;
free(p);
return cwd;
}
bool FileUtil::isdir(std::string const &path)
{
struct stat st;
if (stat(path.c_str(), &st) == 0) {
if (st.st_mode & S_IFDIR) {
return true;
}
}
return false;
}
-void FileUtil::rmdir(const std::string &dir)
+void FileUtil::rmdir(std::string const &dir)
{
::rmdir(dir.c_str());
}
-void FileUtil::rmfile(const std::string &path)
+void FileUtil::rmfile(std::string const &path)
{
unlink(path.c_str());
}
-void FileUtil::mkdir(const std::string &dir, int perm)
+void FileUtil::mkdir(std::string const &dir, int perm)
{
::mkdir(dir.c_str(), perm);
}
-bool FileUtil::chdir(const std::string &dir)
+bool FileUtil::chdir(std::string const &dir)
{
return ::chdir(dir.c_str()) == 0;
}
void FileUtil::mv(std::string const &src, std::string const &dst)
{
rename(src.c_str(), dst.c_str());
}
void FileUtil::getdirents(std::string const &loc, std::vector<DirEnt> *out)
{
out->clear();
DIR *dir = opendir(loc.c_str());
if (dir) {
while (dirent *d = readdir(dir)) {
DirEnt de;
de.name = d->d_name;
de.isdir = false;
#ifdef DT_DIR
if (d->d_type & DT_DIR) {
#else
stat(d->d_name, &s);
if(S_ISDIR(s.st_mode)) {
#endif
if (de.name == "." || de.name == "..") {
continue;
}
de.isdir = true;
}
out->push_back(de);
}
closedir(dir);
}
}
#endif
-void FileUtil::rmtree(const std::string &path)
+void FileUtil::rmtree(std::string const &path)
{
std::vector<DirEnt> ents;
getdirents(path, &ents);
for (DirEnt const &e : ents) {
if (e.isdir) {
rmtree(path / e.name);
} else {
rmfile(path / e.name);
}
}
rmdir(path);
}
#ifdef _WIN32
std::string FileUtil::normalize_path_separator(std::string const &s)
{
if (!s.empty()) {
size_t n = s.size();
std::vector<char> v;
v.reserve(n);
for (size_t i = 0; i < n; i++) {
char c = s[i];
if (c == '/') {
c = '\\';
}
v.push_back(c);
}
char const *p = &v[0];
return std::string(p, p + n);
}
return std::string();
}
#else
std::string FileUtil::normalize_path_separator(std::string const &s)
{
return s;
}
#endif
static std::string which(std::string const &name, std::string const &dir)
{
std::vector<FileUtil::DirEnt> ents;
FileUtil::getdirents(dir, &ents);
for (FileUtil::DirEnt const &ent : ents) {
if (!ent.isdir) {
#ifdef _WIN32
if (stricmp(ent.name.c_str(), name.c_str()) == 0) {
return FileUtil::normalize_path_separator(dir / ent.name);
}
#else
if (ent.name == name) {
return dir / ent.name;
}
#endif
}
}
return std::string();
}
static void split_path(std::string const &path, std::vector<std::string> *out)
{
char const *begin = path.c_str();
char const *end = begin + path.size();
char const *ptr = begin;
char const *left = ptr;
#ifdef _WIN32
const int sep = ';';
#else
const int sep = ':';
#endif
while (1) {
int c = 0;
if (ptr < end) {
c = *ptr & 0xff;
}
if (c == sep || c == 0) {
if (left < ptr) {
out->push_back(std::string(left, ptr));
}
if (c == 0) break;
ptr++;
left = ptr;
} else {
ptr++;
}
}
}
std::string FileUtil::which(std::string const &name, std::vector<std::string> *out)
{
if (out) {
out->clear();
}
char const *p = getenv("PATH");
if (p) {
std::vector<std::string> paths;
split_path(p, &paths);
for (std::string const &path : paths) {
std::string t = ::which(name, path);
if (!t.empty()) {
if (out) {
out->push_back(t);
} else {
return t;
}
}
}
}
if (out && !out->empty()) {
return out->front();
}
return std::string();
}
diff --git a/src/FileUtil.h b/src/FileUtil.h
index 0c2df36..35d9e7f 100644
--- a/src/FileUtil.h
+++ b/src/FileUtil.h
@@ -1,27 +1,27 @@
#ifndef FILEUTIL_H
#define FILEUTIL_H
#include <string>
#include <vector>
class FileUtil {
public:
struct DirEnt {
std::string name;
bool isdir = false;
};
static std::string getcwd();
static void mkdir(std::string const &dir, int perm = 0777);
- static void rmdir(const std::string &dir);
+ static void rmdir(std::string const &dir);
static bool chdir(std::string const &dir);
- static void rmfile(const std::string &path);
- static void rmtree(const std::string &path);
- static void mv(const std::string &src, const std::string &dst);
+ static void rmfile(std::string const &path);
+ static void rmtree(std::string const &path);
+ static void mv(std::string const &src, std::string const &dst);
static void getdirents(std::string const &loc, std::vector<DirEnt> *out);
- static bool isdir(const std::string &path);
- static std::string which(const std::string &name, std::vector<std::string> *out = nullptr);
- static std::string normalize_path_separator(const std::string &s);
+ static bool isdir(std::string const &path);
+ static std::string which(std::string const &name, std::vector<std::string> *out = nullptr);
+ static std::string normalize_path_separator(std::string const &s);
};
#endif // FILEUTIL_H
diff --git a/src/Git.cpp b/src/Git.cpp
index 4476816..0157980 100644
--- a/src/Git.cpp
+++ b/src/Git.cpp
@@ -1,1431 +1,1431 @@
#include "Git.h"
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QProcess>
#include <QThread>
#include <QTimer>
#include <set>
#include "common/joinpath.h"
#include "common/misc.h"
#include "GitObjectManager.h"
#include "MyProcess.h"
#define DEBUGLOG 0
using callback_t = Git::callback_t;
struct Git::Private {
QString git_command;
std::vector<char> result;
QString error_message;
int process_exit_code = 0;
QString working_repo_dir;
callback_t callback_func = nullptr;
void *callback_cookie = nullptr;
};
Git::Git()
: m(new Private)
{
}
Git::Git(const Context &cx, QString const &repodir)
: m(new Private)
{
setGitCommand(cx.git_command);
setWorkingRepositoryDir(repodir);
}
Git::~Git()
{
delete m;
}
void Git::setLogCallback(callback_t func, void *cookie)
{
m->callback_func = func;
m->callback_cookie = cookie;
}
void Git::setWorkingRepositoryDir(QString const &repo)
{
m->working_repo_dir = repo;
}
QString const &Git::workingRepositoryDir() const
{
return m->working_repo_dir;
}
bool Git::isValidID(QString const &id)
{
int zero = 0;
int n = id.size();
if (n > 2 && n <= GIT_ID_LENGTH) {
ushort const *p = id.utf16();
for (int i = 0; i < n; i++) {
uchar c = p[i];
if (c == '0') {
zero++;
} else if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
// ok
} else {
return false;
}
}
if (zero == GIT_ID_LENGTH) { // 全部 0 の時は false を返す
return false;
}
return true; // ok
}
return false;
}
QByteArray Git::toQByteArray() const
{
if (m->result.empty()) return QByteArray();
return QByteArray(&m->result[0], m->result.size());
}
QString Git::resultText() const
{
return QString::fromUtf8(toQByteArray());
}
void Git::setGitCommand(QString const &path)
{
m->git_command = path;
}
QString Git::gitCommand() const
{
Q_ASSERT(m);
return m->git_command;
}
void Git::clearResult()
{
m->result.clear();
m->process_exit_code = 0;
m->error_message = QString();
}
QString Git::errorMessage() const
{
return m->error_message;
}
int Git::getProcessExitCode() const
{
return m->process_exit_code;
}
bool Git::chdirexec(std::function<bool()> const &fn)
{
bool ok = false;
QDir cwd = QDir::current();
QString dir = workingRepositoryDir();
if (isValidWorkingCopy(dir) && QDir::setCurrent(dir)) {
ok = fn();
QDir::setCurrent(cwd.path());
}
return ok;
}
bool Git::git(QString const &arg, bool chdir, bool errout, AbstractPtyProcess *pty)
{
QFileInfo info(gitCommand());
if (!info.isExecutable()) {
qDebug() << "Invalid git command: " << gitCommand();
return false;
}
#if DEBUGLOG
qDebug() << "exec: git " << arg;
QTime timer;
timer.start();
#endif
clearResult();
auto DoIt = [&](){
QString cmd = QString("\"%1\" --no-pager ").arg(gitCommand());
cmd += arg;
if (m->callback_func) {
QByteArray ba;
ba.append("> git ");
ba.append(arg);
ba.append('\n');
m->callback_func(m->callback_cookie, ba.data(), (int)ba.size());
}
if (pty) {
- pty->start(cmd);
+ pty->start(cmd, pty->userVariant());
m->process_exit_code = 0; // バックグラウンドで実行を継続するけど、とりあえず成功したことにしておく
} else {
Process proc;
proc.start(cmd, false);
m->process_exit_code = proc.wait();
if (errout) {
m->result = proc.errbytes;
} else {
m->result = proc.outbytes;
}
m->error_message = proc.errstring();
}
return m->process_exit_code == 0;
};
bool ok = false;
if (chdir) {
if (pty) {
pty->setChangeDir(workingRepositoryDir());
ok = DoIt();
} else {
ok = chdirexec(DoIt);
}
} else {
ok = DoIt();
}
#if DEBUGLOG
qDebug() << timer.elapsed() << "ms";
#endif
return ok;
}
GitPtr Git::dup() const
{
Git *p = new Git();
p->m->git_command = m->git_command;
p->m->working_repo_dir = m->working_repo_dir;
p->m->callback_func = m->callback_func;
p->m->callback_cookie = m->callback_cookie;
return GitPtr(p);
}
bool Git::isValidWorkingCopy(QString const &dir)
{
return QFileInfo(dir).isDir() && QDir(dir / ".git").exists();
}
bool Git::isValidWorkingCopy() const
{
return isValidWorkingCopy(workingRepositoryDir());
}
QString Git::version()
{
git("--version", false);
return resultText().trimmed();
}
bool Git::init()
{
bool ok = false;
QDir cwd = QDir::current();
QString dir = workingRepositoryDir();
if (QDir::setCurrent(dir)) {
QString gitdir = dir / ".git";
if (!QFileInfo(gitdir).isDir()) {
git("init", false);
if (QFileInfo(gitdir).isDir()) {
ok = true;
}
}
QDir::setCurrent(cwd.path());
}
return ok;
}
QString Git::rev_parse(QString const &name)
{
QString cmd = "rev-parse %1";
cmd = cmd.arg(name);
if (git(cmd)) {
return resultText().trimmed();
}
return QString();
}
QStringList Git::refs()
{
QStringList lines;
{
QString path = workingRepositoryDir() / ".git/packed-refs";
QFile file(path);
if (file.open(QFile::ReadOnly)) {
while (!file.atEnd()) {
QByteArray ba = file.readLine();
QString line = QString::fromLatin1(ba).trimmed();
if (line.indexOf(" refs/") > 0) {
lines.push_back(line);
}
}
}
}
{
QString refspath = workingRepositoryDir() / ".git/refs/tags";
QDirIterator it(refspath);
while (it.hasNext()) {
it.next();
QString path = it.filePath();
if (QFileInfo(path).isFile()) {
QString name = it.fileName();
QFile file(path);
if (file.open(QFile::ReadOnly)) {
QByteArray ba = file.readAll();
QString id = QString::fromLatin1(ba).trimmed();
if (Git::isValidID(id)) {
QString line = id + " refs/tags/" + name;
lines.push_back(line);
}
}
}
}
}
return lines;
}
QList<Git::Tag> Git::tags()
{
auto MidCmp = [](QString const &line, int i, char const *ptr){
ushort const *p = line.utf16();
for (int j = 0; ptr[j]; j++) {
if (p[i + j] != ptr[j]) return false;
}
return true;
};
QList<Git::Tag> list;
QStringList lines = refs();
for (QString const &line : lines) {
Tag tag;
if (line.isEmpty()) continue;
int i = line.indexOf(' ');
if (i == GIT_ID_LENGTH) {
tag.id = line.mid(0, i);
if (MidCmp(line, i, " refs/tags/")) {
tag.name = line.mid(i + 11).trimmed();
if (tag.name.isEmpty()) continue;
list.push_back(tag);
}
}
}
return list;
}
bool Git::tag(QString const &name, QString const &id)
{
QString cmd = "tag \"%1\" %2";
cmd = cmd.arg(name).arg(id);
return git(cmd);
}
void Git::delete_tag(QString const &name, bool remote)
{
QString cmd = "tag --delete \"%1\"";
cmd = cmd.arg(name);
git(cmd);
if (remote) {
QString cmd = "push --delete origin \"%1\"";
cmd = cmd.arg(name);
git(cmd);
}
}
QString Git::diff(QString const &old_id, QString const &new_id)
{
QString cmd = "diff --full-index -a %1 %2";
cmd = cmd.arg(old_id).arg(new_id);
git(cmd);
return resultText();
}
QString Git::diff_file(QString const &old_path, QString const &new_path)
{
QString cmd = "diff --full-index -a -- %1 %2";
cmd = cmd.arg(old_path).arg(new_path);
git(cmd);
return resultText();
}
QList<Git::DiffRaw> Git::diff_raw(QString const &old_id, QString const &new_id)
{
QList<DiffRaw> list;
QString cmd = "diff --raw --abbrev=%1 %2 %3";
cmd = cmd.arg(GIT_ID_LENGTH).arg(old_id).arg(new_id);
git(cmd);
QString text = resultText();
QStringList lines = text.split('\n', QString::SkipEmptyParts);
for (QString const &line : lines) { // raw format: e.g. ":100644 100644 08bc10d... 18f0501... M src/MainWindow.cpp"
DiffRaw item;
int colon = line.indexOf(':');
int tab = line.indexOf('\t');
if (colon >= 0 && colon < tab) {
QStringList header = line.mid(colon + 1, tab - colon - 1).split(' ', QString::SkipEmptyParts); // コロンとタブの間の文字列を空白で分割
if (header.size() >= 5) {
QStringList files = line.mid(tab + 1).split('\t', QString::SkipEmptyParts); // タブより後ろはファイルパス
if (!files.empty()) {
for (QString &file : files) {
file = Git::trimPath(file);
}
item.a.id = header[2];
item.b.id = header[3];
item.a.mode = header[0];
item.b.mode = header[1];
item.state = header[4];
item.files = files;
list.push_back(item);
}
}
}
}
return list;
}
QString Git::diff_to_file(QString const &old_id, QString const &path)
{
#if 1
QString cmd = "diff --full-index -a %1 -- \"%2\"";
cmd = cmd.arg(old_id).arg(path);
git(cmd);
return resultText();
#else
return diff_(old_id, new_id);
#endif
}
QString Git::getCurrentBranchName()
{
if (isValidWorkingCopy()) {
git("rev-parse --abbrev-ref HEAD");
QString s = resultText().trimmed();
if (!s.isEmpty() && !s.startsWith("fatal:") && s.indexOf(' ') < 0) {
return s;
}
}
return QString();
}
QStringList Git::getUntrackedFiles()
{
QStringList files;
Git::FileStatusList stats = status();
for (FileStatus const &s : stats) {
if (s.code() == FileStatusCode::Untracked) {
files.push_back(s.path1());
}
}
return files;
}
void Git::parseAheadBehind(QString const &s, Branch *b)
{
ushort const *begin = s.utf16();
ushort const *end = begin + s.size();
ushort const *ptr = begin;
auto NCompare = [](ushort const *a, char const *b, size_t n){
for (size_t i = 0; i < n; i++) {
if (*a < (unsigned char)*b) return false;
if (*a > (unsigned char)*b) return false;
if (!*a) break;
a++;
b++;
}
return true;
};
auto SkipSpace = [&](){
while (ptr < end && QChar(*ptr).isSpace()) {
ptr++;
}
};
auto GetNumber = [&](){
int n = 0;
while (ptr < end && QChar(*ptr).isDigit()) {
n = n * 10 + (*ptr - '0');
ptr++;
}
return n;
};
if (*ptr == '[') {
ptr++;
ushort const *e = nullptr;
int n;
for (n = 0; ptr + n < end; n++) {
if (ptr[n] == ']') {
e = ptr + n;
break;
}
}
if (e) {
end = e;
while (1) {
if (NCompare(ptr, "ahead ", 6)) {
ptr += 6;
SkipSpace();
b->ahead = GetNumber();
} else if (NCompare(ptr, "behind ", 7)) {
ptr += 7;
SkipSpace();
b->behind = GetNumber();
} else {
ushort c = 0;
if (ptr < end) {
c = *ptr;
}
if (c == 0) break;
ptr++;
}
}
}
}
}
QList<Git::Branch> Git::branches()
{
QList<Branch> branches;
git(QString("branch -vv -a --abbrev=%1").arg(GIT_ID_LENGTH));
QString s = resultText();
#if DEBUGLOG
qDebug() << s;
#endif
QStringList lines = misc::splitLines(s);
for (QString const &line : lines) {
if (line.size() > 2) {
QString name;
QString text = line.mid(2);
int pos = 0;
if (text.startsWith('(')) {
int i, paren = 1;
for (i = 1; i < text.size(); i++) {
if (text[i] == '(') {
paren++;
} else if (text[i] == ')') {
if (paren > 0) {
paren--;
if (paren == 0) {
pos = i;
break;
}
}
}
}
// pos = text.indexOf(')');
if (pos > 1) {
name = text.mid(1, pos - 1);
pos++;
}
} else {
while (pos < text.size() && !QChar::isSpace(text.utf16()[pos])) {
pos++;
}
name = text.mid(0, pos);
}
if (!name.isEmpty()) {
while (pos < text.size() && QChar::isSpace(text.utf16()[pos])) {
pos++;
}
text = text.mid(pos);
Branch b;
if (name.startsWith("HEAD detached at ")) {
b.flags |= Branch::HeadDetachedAt;
name = name.mid(17);
}
if (name.startsWith("HEAD detached from ")) {
b.flags |= Branch::HeadDetachedFrom;
name = name.mid(19);
}
if (name.startsWith("remotes/")) {
name = name.mid(8);
int i = name.indexOf('/');
if (i > 0) {
b.remote = name.mid(0, i);
name = name.mid(i + 1);
}
}
b.name = name;
if (text.startsWith("-> ")) {
b.id = ">" + text.mid(3);
} else {
int i = text.indexOf(' ');
if (i == GIT_ID_LENGTH) {
b.id = text.mid(0, GIT_ID_LENGTH);
}
while (i < text.size() && QChar::isSpace(text.utf16()[i])) {
i++;
}
text = text.mid(i);
parseAheadBehind(text, &b);
}
if (line.startsWith('*')) {
b.flags |= Branch::Current;
}
branches.push_back(b);
}
}
}
for (int i = 0; i < branches.size(); i++) {
Branch *b = &branches[i];
if (b->id.startsWith('>')) {
QString name = b->id.mid(1);
for (int j = 0; j < branches.size(); j++) {
if (j != i) {
if (branches[j].name == name) {
branches[i].id = branches[j].id;
break;
}
}
}
}
}
return branches;
}
Git::CommitItemList Git::log_all(QString const &id, int maxcount)
{
CommitItemList items;
QString text;
QString cmd = "log --pretty=format:\"commit:%H#gpg:%G?#key:%GK#parent:%P#author:%an#mail:%ae#date:%ci##%s\" --all -%1 %2";
cmd = cmd.arg(maxcount).arg(id);
git(cmd);
if (getProcessExitCode() == 0) {
text = resultText().trimmed();
QStringList lines = misc::splitLines(text);
for (QString const &line : lines) {
int i = line.indexOf("##");
if (i > 0) {
Git::CommitItem item;
QString signed_key;
item.message = line.mid(i + 2);
QStringList atts = line.mid(0, i).split('#');
for (QString const &s : atts) {
int j = s.indexOf(':');
if (j > 0) {
QString key = s.mid(0, j);
QString val = s.mid(j + 1);
if (key == "commit") {
item.commit_id = val;
} else if (key == "gpg") {
item.signature = *val.utf16();
} else if (key == "key") {
signed_key = val;
} else if (key == "parent") {
item.parent_ids = val.split(' ', QString::SkipEmptyParts);
} else if (key == "author") {
item.author = val;
} else if (key == "mail") {
item.email = val;
} else if (key == "date") {
item.commit_date = QDateTime::fromString(val, Qt::ISODate).toLocalTime();
} else if (key == "debug") {
}
}
}
if (!signed_key.isEmpty()) {
ushort const *begin = signed_key.utf16();
int n = signed_key.size();
for (int i = 0; i + 1 < n; i += 2) {
ushort c = begin[i];
ushort d = begin[i + 1];
if (c < 0x80 && isxdigit(c) && isxdigit(d)) {
char tmp[3];
tmp[0] = c;
tmp[1] = d;
tmp[2] = 0;
int v = strtol(tmp, nullptr, 16);
item.fingerprint.push_back(v);
} else {
break;
}
}
}
items.push_back(item);
}
}
}
return items;
}
Git::CommitItemList Git::log(int maxcount)
{
return log_all(QString(), maxcount);
}
bool Git::queryCommit(QString const &id, CommitItem *out)
{
if (objectType(id) == "commit") {
out->commit_id = id;
QByteArray ba;
if (cat_file(id, &ba)) {
QStringList lines = misc::splitLines(ba, [](char const *p, size_t n){
return QString::fromUtf8(p, (int)n);
});
while (!lines.empty() && lines[lines.size() - 1].isEmpty()) {
lines.pop_back();
}
int i;
for (i = 0; i < lines.size(); i++) {
QString const &line = lines[i];
if (line.isEmpty()) {
i++;
for (; i < lines.size(); i++) {
QString const &line = lines[i];
if (!out->message.isEmpty()) {
out->message.append('\n');
}
out->message.append(line);
}
break;
}
if (line.startsWith("parent ")) {
out->parent_ids.push_back(line.mid(7));
} else if (line.startsWith("author ")) {
QStringList arr = misc::splitWords(line);
int n = arr.size();
if (n > 4) {
n -= 2;
out->commit_date = QDateTime::fromTime_t(atol(arr[n].toStdString().c_str()));
n--;
out->email = arr[n];
if (out->email.startsWith('<') && out->email.endsWith('>')) {
int n = out->email.size();
out->email = out->email.mid(1, n - 2);
}
for (int i = 1; i < n; i++) {
if (!out->author.isEmpty()) {
out->author += ' ';
}
out->author += arr[i];
}
}
}
}
}
return true;
}
return false;
}
Git::CloneData Git::preclone(QString const &url, QString const &path)
{
CloneData d;
d.url = url;
if (path.endsWith('/') || path.endsWith('\\')) {
auto GitBaseName = [](QString location){
int i = location.lastIndexOf('/');
int j = location.lastIndexOf('\\');
if (i < j) i = j;
i = i < 0 ? 0 : (i + 1);
j = location.size();
if (location.endsWith(".git")) {
j -= 4;
}
if (i < j) {
location = location.mid(i, j - i);
}
return location;
};
d.basedir = path;
d.subdir = GitBaseName(url);
} else {
QFileInfo info(path);
d.basedir = info.dir().path();
d.subdir = info.fileName();
}
return d;
}
bool Git::clone(CloneData const &data, AbstractPtyProcess *pty)
{
QString clone_to = data.basedir / data.subdir;
m->working_repo_dir = misc::normalizePathSeparator(clone_to);
bool ok = false;
QDir cwd = QDir::current();
auto DoIt = [&](){
QString cmd = "clone --progress \"%1\" \"%2\"";
cmd = cmd.arg(data.url).arg(data.subdir);
ok = git(cmd, false, true, pty);
};
if (pty) {
pty->setChangeDir(data.basedir);
DoIt();
} else {
if (QDir::setCurrent(data.basedir)) {
DoIt();
QDir::setCurrent(cwd.path());
}
}
return ok;
}
QString Git::encodeQuotedText(QString const &str)
{
std::vector<ushort> vec;
ushort const *begin = str.utf16();
ushort const *end = begin + str.size();
ushort const *ptr = begin;
vec.push_back('\"');
while (ptr < end) {
ushort c = *ptr;
ptr++;
if (c == '\"') { // triple quotes
vec.push_back(c);
vec.push_back(c);
vec.push_back(c);
} else {
vec.push_back(c);
}
}
vec.push_back('\"');
return QString::fromUtf16(&vec[0], vec.size());
}
bool Git::commit_(QString const &msg, bool amend, bool sign, AbstractPtyProcess *pty)
{
QString cmd = "commit";
if (amend) {
cmd += " --amend";
}
if (sign) {
cmd += " -S";
}
QString text = msg.trimmed();
if (text.isEmpty()) {
text = "no message";
}
text = encodeQuotedText(text);
cmd += QString(" -m %1").arg(text);
return git(cmd, true, false, pty);
}
bool Git::commit(QString const &text, bool sign, AbstractPtyProcess *pty)
{
return commit_(text, false, sign, pty);
}
bool Git::commit_amend_m(QString const &text, bool sign, AbstractPtyProcess *pty)
{
return commit_(text, true, sign, pty);
}
bool Git::revert(QString const &id)
{
QString cmd = "revert %1";
cmd = cmd.arg(id);
return git(cmd);
}
void Git::push_u(QString const &remote, QString const &branch, AbstractPtyProcess *pty)
{
if (remote.indexOf('\"') >= 0 || branch.indexOf('\"') >= 0) {
return;
}
QString cmd = "push -u \"%1\" \"%2\"";
git(cmd.arg(remote).arg(branch), true, false, pty);
}
bool Git::push_(bool tags, AbstractPtyProcess *pty)
{
QString cmd = "push";
if (tags) {
cmd += " --tags";
}
return git(cmd, true, false, pty);
}
bool Git::push(bool tags, AbstractPtyProcess *pty)
{
return push_(tags, pty);
}
Git::FileStatusList Git::status_()
{
FileStatusList files;
if (git("status -s -u --porcelain")) {
QString text = resultText();
QStringList lines = misc::splitLines(text);
for (QString const &line : lines) {
if (line.size() > 3) {
FileStatus s(line);
if (s.code() != FileStatusCode::Unknown) {
files.push_back(s);
}
}
}
}
return files;
}
Git::FileStatusList Git::status()
{
return status_();
}
QString Git::objectType(QString const &id)
{
git("cat-file -t " + id);
return resultText().trimmed();
}
QByteArray Git::cat_file_(QString const &id)
{
git("cat-file -p " + id);
return toQByteArray();
}
bool Git::cat_file(QString const &id, QByteArray *out)
{
if (isValidID(id)) {
*out = cat_file_(id);
return true;
}
return false;
}
void Git::resetFile(QString const &path)
{
git("checkout -- " + path);
}
void Git::resetAllFiles()
{
git("reset --hard HEAD");
}
void Git::removeFile(QString const &path)
{
git("rm " + path);
}
void Git::stage(QString const &path)
{
git("add " + path);
}
void Git::stage(QStringList const &paths)
{
QString cmd = "add";
for (QString const &path : paths) {
cmd += ' ';
cmd += '\"';
cmd += path;
cmd += '\"';
}
git(cmd);
}
void Git::unstage(QString const &path)
{
QString cmd = "reset HEAD \"%1\"";
git(cmd.arg(path));
}
void Git::unstage(QStringList const &paths)
{
QString cmd = "reset HEAD";
for (QString const &path : paths) {
cmd += " \"";
cmd += path;
cmd += '\"';
}
git(cmd);
}
void Git::pull(AbstractPtyProcess *pty)
{
git("pull", true, false, pty);
}
void Git::fetch(AbstractPtyProcess *pty)
{
git("fetch", true, false, pty);
}
void Git::rebaseOnto(QString const &newbase, QString const &upstream, QString const &branch, AbstractPtyProcess *pty)
{
QString cmd = "rebase --onto \"%1\" \"%2\" \"%3\"";
cmd = cmd.arg(newbase).arg(upstream).arg(branch);
git(cmd, true, false, pty);
}
QStringList Git::make_branch_list_()
{
QStringList list;
QStringList l = misc::splitLines(resultText());
for (QString const &s : l) {
if (s.startsWith("* ")) list.push_back(s.mid(2));
if (s.startsWith(" ")) list.push_back(s.mid(2));
}
return list;
}
void Git::createBranch(QString const &name)
{
git("branch " + name);
}
void Git::checkoutBranch(QString const &name)
{
git("checkout " + name);
}
void Git::cherrypick(QString const &name)
{
git("cherry-pick " + name);
}
void Git::mergeBranch(QString const &name)
{
git("merge " + name);
}
void Git::rebaseBranch(QString const &name)
{
git("rebase " + name);
}
QStringList Git::getRemotes()
{
QStringList ret;
git("remote");
QStringList lines = misc::splitLines(resultText());
for (QString const &line: lines) {
if (!line.isEmpty()) {
ret.push_back(line);
}
}
return ret;
}
Git::User Git::getUser(Source purpose)
{
User user;
bool global = purpose == Git::Source::Global;
bool local = purpose == Git::Source::Local;
QString arg1;
if (global) arg1 = "--global";
if (local) arg1 = "--local";
bool chdir = !global;
if (git(QString("config %1 user.name").arg(arg1), chdir)) {
user.name = resultText().trimmed();
}
if (git(QString("config %1 user.email").arg(arg1), chdir)) {
user.email = resultText().trimmed();
}
return user;
}
void Git::setUser(const User &user, bool global)
{
bool chdir = !global;
git(QString("config %1 user.name %2").arg(global ? "--global" : "").arg(encodeQuotedText(user.name)), chdir);
git(QString("config %1 user.email %2").arg(global ? "--global" : "").arg(encodeQuotedText(user.email)), chdir);
}
bool Git::reset_head1()
{
return git("reset HEAD~1");
}
bool Git::rm_cached(QString const &file)
{
QString cmd = "rm --cached \"%1\"";
return git(cmd.arg(file));
}
void Git::getRemoteURLs(QList<Remote> *out)
{
out->clear();
git("remote -v");
QStringList lines = misc::splitLines(resultText());
for (QString const &line : lines) {
int i = line.indexOf('\t');
int j = line.indexOf(" (");
if (i > 0 && i < j) {
Remote r;
r.name = line.mid(0, i);
r.url = line.mid(i + 1, j - i - 1);
r.purpose = line.mid(j + 1);
if (r.purpose.startsWith('(') && r.purpose.endsWith(')')) {
r.purpose = r.purpose.mid(1, r.purpose.size() - 2);
}
out->push_back(r);
}
}
}
void Git::setRemoteURL(QString const &name, QString const &url)
{
QString cmd = "remote set-url %1 %2";
cmd = cmd.arg(encodeQuotedText(name)).arg(encodeQuotedText(url));
git(cmd);
}
void Git::addRemoteURL(QString const &name, QString const &url)
{
QString cmd = "remote add \"%1\" \"%2\"";
cmd = cmd.arg(encodeQuotedText(name)).arg(encodeQuotedText(url));
git(cmd);
}
void Git::removeRemote(QString const &name)
{
QString cmd = "remote remove %1";
cmd = cmd.arg(encodeQuotedText(name));
git(cmd);
}
bool Git::reflog(ReflogItemList *out, int maxcount)
{
out->clear();
QString cmd = "reflog --no-abbrev --raw -n %1";
cmd = cmd.arg(maxcount);
if (!git(cmd)) return false;
QByteArray ba = toQByteArray();
if (!ba.isEmpty()) {
ReflogItem item;
char const *begin = ba.data();
char const *end = begin + ba.size();
char const *left = begin;
char const *ptr = begin;
while (1) {
int c = 0;
if (ptr < end) {
c = *ptr;
}
if (c == '\r' || c == '\n' || c == 0) {
int d = 0;
QString line = QString::fromUtf8(left, ptr - left);
if (left < ptr) {
d = *left & 0xff;
}
if (d == ':') {
// ex. ":100644 100644 bb603836fb597cca994309a1f0a52251d6b20314 d6b9798854debee375bb419f0f2ed9c8437f1932 M\tsrc/MainWindow.cpp"
int tab = line.indexOf('\t');
if (tab > 1) {
QString tmp = line.mid(1, tab - 1);
QString path = line.mid(tab + 1);
QStringList cols = misc::splitWords(tmp);
if (!path.isEmpty() && cols.size() == 5) {
ReflogItem::File file;
file.atts_a = cols[0];
file.atts_b = cols[1];
file.id_a = cols[2];
file.id_b = cols[3];
file.type = cols[4];
file.path = path;
item.files.push_back(file);
}
}
} else {
bool start = isxdigit(d);
if (start || c == 0) {
if (!item.id.isEmpty()) {
out->push_back(item);
}
}
if (start) {
// ex. "0a2a8b6b66f48bcbf985d8a2afcd14ff41676c16 HEAD@{188}: commit: comment text"
item = ReflogItem();
int i = line.indexOf(": ");
if (i > 0) {
int j = line.indexOf(": ", i + 2);
if (j > 2) {
item.head = line.mid(0, i);
item.command = line.mid(i + 2, j - i - 2);
item.comment= line.mid(j + 2);
if (item.head.size() > GIT_ID_LENGTH) {
item.id = item.head.mid(0, GIT_ID_LENGTH);
item.head = item.head.mid(GIT_ID_LENGTH + 1);
}
}
}
}
}
if (c == 0) break;
ptr++;
left = ptr;
} else {
ptr++;
}
}
}
return true;
}
// Git::FileStatus
QString Git::trimPath(QString const &s)
{
ushort const *begin = s.utf16();
ushort const *end = begin + s.size();
ushort const *left = begin;
ushort const *right = end;
while (left < right && QChar(*left).isSpace()) left++;
while (left < right && QChar(right[-1]).isSpace()) right--;
if (left + 1 < right && *left == '\"' && right[-1] == '\"') { // if quoted ?
left++;
right--;
QByteArray ba;
ushort const *ptr = left;
while (ptr < right) {
ushort c = *ptr;
ptr++;
if (c == '\\') {
c = 0;
while (ptr < right && QChar(*ptr).isDigit()) { // decode \oct
c = c * 8 + (*ptr - '0');
ptr++;
}
}
ba.push_back(c);
}
return QString::fromUtf8(ba);
}
if (left == begin && right == end) return s;
return QString::fromUtf16(left, right - left);
}
Git::FileStatusCode Git::FileStatus::parseFileStatusCode(char x, char y)
{
if (x == ' ' && (y == 'M' || y == 'D')) return FileStatusCode::NotUpdated;
if (x == 'M' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::UpdatedInIndex;
if (x == 'A' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::AddedToIndex;
if (x == 'D' && (y == 'M' || y == ' ')) return FileStatusCode::DeletedFromIndex;
if (x == 'R' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::RenamedInIndex;
if (x == 'C' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::CopiedInIndex;
if (x == 'C' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::CopiedInIndex;
if (x == 'D' && y == 'D') return FileStatusCode::Unmerged_BothDeleted;
if (x == 'A' && y == 'U') return FileStatusCode::Unmerged_AddedByUs;
if (x == 'U' && y == 'D') return FileStatusCode::Unmerged_DeletedByThem;
if (x == 'U' && y == 'A') return FileStatusCode::Unmerged_AddedByThem;
if (x == 'D' && y == 'U') return FileStatusCode::Unmerged_DeletedByUs;
if (x == 'A' && y == 'A') return FileStatusCode::Unmerged_BothAdded;
if (x == 'U' && y == 'U') return FileStatusCode::Unmerged_BothModified;
if (x == '?' && y == '?') return FileStatusCode::Untracked;
if (x == '!' && y == '!') return FileStatusCode::Ignored;
return FileStatusCode::Unknown;
}
void Git::FileStatus::parse(QString const &text)
{
data = Data();
if (text.size() > 3) {
ushort const *p = text.utf16();
if (p[2] == ' ') {
data.code_x = p[0];
data.code_y = p[1];
int i = text.indexOf(" -> ", 3);
if (i > 3) {
data.rawpath1 = text.mid(3, i - 3);
data.rawpath2 = text.mid(i + 4);
data.path1 = trimPath(data.rawpath1);
data.path2 = trimPath(data.rawpath2);
} else {
data.rawpath1 = text.mid(3);
data.path1 = trimPath(data.rawpath1);
data.path2 = QString();
}
data.code = parseFileStatusCode(data.code_x, data.code_y);
}
}
}
QByteArray Git::blame(QString const &path)
{
QString cmd = "blame --abbrev=40 \"%1\"";
cmd = cmd.arg(path);
if (git(cmd)) {
return toQByteArray();
}
return QByteArray();
}
QList<Git::RemoteInfo> Git::ls_remote()
{
QList<RemoteInfo> list;
QString cmd = "ls-remote";
if (git(cmd)) {
QStringList lines = misc::splitLines(resultText());
for (QString const &line : lines) {
QStringList words = misc::splitWords(line);
if (words.size() == 2 && isValidID(words[0])) {
RemoteInfo info;
info.commit_id = words[0];
info.name = words[1];
list.push_back(info);
}
}
}
return list;
}
QString Git::signingKey(Source purpose)
{
QString arg1;
if (purpose == Source::Global) arg1 = "--global";
if (purpose == Source::Local) arg1 = "--local";
QString cmd = "config %1 user.signingkey";
cmd = cmd.arg(arg1);
bool chdir = purpose != Source::Global;
if (git(cmd, chdir)) {
return resultText().trimmed();
}
return QString();
}
bool Git::setSigningKey(QString const &id, bool global)
{
for (auto i : id) {
if (!QChar(i).isLetterOrNumber()) return false;
}
QString cmd = "config %1 %2 user.signingkey %3";
cmd = cmd.arg(global ? "--global" : "--local").arg(id.isEmpty() ? "--unset" : "").arg(id);
return git(cmd, !global);
}
Git::SignPolicy Git::signPolicy(Source source)
{
QString arg1;
if (source == Source::Global) arg1 = "--global";
if (source == Source::Local) arg1 = "--local";
QString cmd = "config %1 commit.gpgsign";
cmd = cmd.arg(arg1);
bool chdir = source != Source::Global;
if (git(cmd, chdir)) {
QString t = resultText().trimmed();
if (t == "false") return SignPolicy::False;
if (t == "true") return SignPolicy::True;
}
return SignPolicy::Unset;
}
bool Git::setSignPolicy(Source source, SignPolicy policy)
{
QString arg1;
if (source == Source::Global) arg1 = "--global";
if (source == Source::Local) arg1 = "--local";
QString cmd = "config %1 %2 commit.gpgsign %3";
QString arg2;
QString arg3;
if (policy == SignPolicy::False) {
arg3 = "false";
} else if (policy == SignPolicy::True) {
arg3 = "true";
} else {
arg2 = "--unset";
}
cmd = cmd.arg(arg1).arg(arg2).arg(arg3);
bool chdir = source != Source::Global;
return git(cmd, chdir);
}
bool Git::configGpgProgram(QString const &path, bool global)
{
QString cmd = "config ";
if (global) {
cmd += "--global ";
}
if (path.isEmpty()) {
cmd += "--unset ";
}
cmd += "gpg.program ";
if (!path.isEmpty()) {
cmd += QString("\"%1\"").arg(path);
}
return git(cmd, false);
}
// Diff
void Git::Diff::makeForSingleFile(Git::Diff *diff, QString const &id_a, QString const &id_b, QString const &path, QString const &mode)
{
diff->diff = QString("diff --git a/%1 b/%2").arg(path).arg(path);
diff->index = QString("index %1..%2 %3").arg(id_a).arg(id_b).arg(0);
diff->blob.a_id = id_a;
diff->blob.b_id = id_b;
diff->path = path;
diff->mode = mode;
diff->type = Git::Diff::Type::Create;
}
//
void parseDiff(std::string const &s, Git::Diff const *info, Git::Diff *out)
{
std::vector<std::string> lines;
{
char const *begin = s.c_str();
char const *end = begin + s.size();
misc::splitLines(begin, end, &lines, false);
}
out->diff = QString("diff --git ") + ("a/" + info->path) + ' ' + ("b/" + info->path);
out->index = QString("index ") + info->blob.a_id + ".." + info->blob.b_id + ' ' + info->mode;
out->path = info->path;
out->blob = info->blob;
bool atat = false;
for (std::string const &line : lines) {
int c = line[0] & 0xff;
if (c == '@') {
if (strncmp(line.c_str(), "@@ ", 3) == 0) {
out->hunks.push_back(Git::Hunk());
out->hunks.back().at = line;
atat = true;
}
} else {
if (atat && c == '\\') { // e.g. \ No newline at end of file...
// ignore this line
} else {
if (atat) {
if (c == ' ' || c == '-' || c == '+') {
// nop
} else {
atat = false;
}
}
if (atat) {
if (!out->hunks.isEmpty()) {
out->hunks.back().lines.push_back(line);
}
}
}
}
}
}
diff --git a/src/GitDiff.h b/src/GitDiff.h
index 36a9aeb..7c9a5f2 100644
--- a/src/GitDiff.h
+++ b/src/GitDiff.h
@@ -1,63 +1,63 @@
#ifndef GITDIFF_H
#define GITDIFF_H
#include <set>
#include "common/misc.h"
#include "Git.h"
#include "GitObjectManager.h"
class GitDiff {
friend class CommitListThread;
public:
private:
class LookupTable;
private:
GitObjectCache *objcache = nullptr;
QList<Git::Diff> diffs;
bool interrupted = false;
struct Interrupted {
};
void checkInterrupted()
{
if (interrupted) {
throw Interrupted();
}
}
using MapList = std::list<LookupTable>;
GitPtr git();
static void AddItem(Git::Diff *item, QList<Git::Diff> *diffs);
- void retrieveCompleteTree(QString const &dir, const GitTreeItemList *files, std::map<QString, GitTreeItem> *out);
- void retrieveCompleteTree(QString const &dir, const GitTreeItemList *files);
+ void retrieveCompleteTree(QString const &dir, GitTreeItemList const *files, std::map<QString, GitTreeItem> *out);
+ void retrieveCompleteTree(QString const &dir, GitTreeItemList const *files);
public:
GitDiff(GitObjectCache *objcache)
{
this->objcache = objcache;
}
- bool diff(const QString &id, QList<Git::Diff> *out);
+ bool diff(QString const &id, QList<Git::Diff> *out);
bool diff_uncommited(QList<Git::Diff> *out);
void interrupt()
{
interrupted = true;
}
public:
static QString diffObjects(GitPtr g, QString const &a_id, QString const &b_id);
static QString diffFiles(GitPtr g, QString const &a_path, QString const &b_path);
static void parseDiff(std::string const &s, const Git::Diff *info, Git::Diff *out);
static QString makeKey(QString const &a_id, QString const &b_id);
static QString makeKey(const Git::Diff &diff);
static QString prependPathPrefix(QString const &path);
};
#endif // GITDIFF_H
diff --git a/src/GitHubAPI.h b/src/GitHubAPI.h
index 8616b4e..0335186 100644
--- a/src/GitHubAPI.h
+++ b/src/GitHubAPI.h
@@ -1,62 +1,62 @@
#ifndef GITHUBAPI_H
#define GITHUBAPI_H
#include <QImage>
#include <QThread>
#include <string>
#include <functional>
#include <memory>
class WebContext;
class WebClient;
class GitHubAPI {
public:
using WebClientPtr = std::shared_ptr<WebClient>;
struct User {
std::string login;
std::string avatar_url;
std::string name;
std::string email;
};
struct SearchResultItem {
std::string full_name;
std::string description;
std::string ssh_url;
std::string clone_url;
std::string html_url;
double score = 0;
};
WebContext *webcx;
GitHubAPI(WebContext *webcx)
: webcx(webcx)
{
}
- QList<GitHubAPI::SearchResultItem> searchRepository(const std::string &q);
+ QList<GitHubAPI::SearchResultItem> searchRepository(std::string const &q);
};
class GitHubRequestThread : public QThread {
private:
struct Private;
Private *m;
protected:
void run() override;
public:
GitHubAPI::WebClientPtr web();
public:
GitHubRequestThread();
~GitHubRequestThread() override;
std::string url;
bool ok = false;
std::string text;
std::function<bool(std::string const &text)> callback;
void start(WebContext *webcx);
};
#endif // GITHUBAPI_H
diff --git a/src/GitObjectManager.h b/src/GitObjectManager.h
index 86c7a5d..a661a4c 100644
--- a/src/GitObjectManager.h
+++ b/src/GitObjectManager.h
@@ -1,129 +1,129 @@
#ifndef GITOBJECTMANAGER_H
#define GITOBJECTMANAGER_H
#include <QMutex>
#include <QString>
#include "GitPack.h"
#include "GitPackIdxV2.h"
#include <map>
class GitPackIdxV2;
class Git;
using GitPtr = std::shared_ptr<Git>;
class GitObjectManager {
friend class GitObjectCache;
private:
GitPtr g;
QMutex mutex;
QString subdir_git_objects;
QString subdir_git_objects_pack;
std::vector<GitPackIdxPtr> git_idx_list;
QString workingDir();
- static void applyDelta(const QByteArray *base, const QByteArray *delta, QByteArray *out);
- static bool loadPackedObject(const GitPackIdxPtr &idx, QIODevice *packfile, const GitPackIdxItem *item, GitPack::Object *out);
- bool extractObjectFromPackFile(const GitPackIdxPtr &idx, const GitPackIdxItem *item, GitPack::Object *out);
+ static void applyDelta(QByteArray const *base, QByteArray const *delta, QByteArray *out);
+ static bool loadPackedObject(GitPackIdxPtr const &idx, QIODevice *packfile, GitPackIdxItem const *item, GitPack::Object *out);
+ bool extractObjectFromPackFile(GitPackIdxPtr const &idx, GitPackIdxItem const *item, GitPack::Object *out);
bool extractObjectFromPackFile(QString const &id, QByteArray *out, Git::Object::Type *type);
void loadIndexes();
QString findObjectPath(QString const &id);
bool loadObject(QString const &id, QByteArray *out, Git::Object::Type *type);
GitPtr git()
{
return g;
}
public:
GitObjectManager();
void setup(GitPtr g);
bool catFile(QString const &id, QByteArray *out, Git::Object::Type *type);
void clearIndexes();
};
class GitObjectCache {
public:
struct Item {
QString id;
QByteArray ba;
Git::Object::Type type;
};
private:
GitObjectManager object_manager;
using ItemPtr = std::shared_ptr<Item>;
std::vector<ItemPtr> items;
std::map<QString, QString> revparsemap;
size_t size() const;
public:
GitPtr git()
{
return object_manager.git();
}
void setup(GitPtr g);
QString revParse(QString const &name);
Git::Object catFile(QString const &id);
QString getCommitIdFromTag(QString const &tag);
};
class GitCommit {
public:
QString tree_id;
QStringList parents;
bool parseCommit(GitObjectCache *objcache, QString const &id);
};
struct GitTreeItem {
enum Type {
UNKNOWN,
TREE,
BLOB,
};
Type type = UNKNOWN;
QString name;
QString id;
QString mode;
QString to_string_() const
{
QString t;
switch (type) {
case TREE: t = "TREE"; break;
case BLOB: t = "BLOB"; break;
}
return QString("GitTreeItem:{ %1 %2 %3 %4 }").arg(t).arg(id).arg(mode).arg(name);
}
};
using GitTreeItemList = QList<GitTreeItem>;
class GitCommitTree {
private:
GitObjectCache *objcache;
GitTreeItemList root_item_list;
std::map<QString, GitTreeItem> blob_map;
std::map<QString, QString> tree_id_map;
GitPtr git();
QString lookup_(QString const &file, GitTreeItem *out);
public:
GitCommitTree(GitObjectCache *objcache);
QString lookup(QString const &file);
bool lookup(QString const &file, GitTreeItem *out);
void parseTree(QString const &tree_id);
QString parseCommit(QString const &commit_id);
GitTreeItemList const *treelist() const
{
return &root_item_list;
}
};
QString lookupFileID(GitObjectCache *objcache, QString const &commit_id, QString const &file);
#endif // GITOBJECTMANAGER_H
diff --git a/src/GitPack.cpp b/src/GitPack.cpp
index e052815..cd76cd1 100644
--- a/src/GitPack.cpp
+++ b/src/GitPack.cpp
@@ -1,252 +1,252 @@
#include "GitPack.h"
#include <zlib.h>
#include <QDebug>
#include <QFile>
#include "GitPackIdxV2.h"
void GitPack::decodeTree(QByteArray *out)
{
if (out && out->size() > 0) {
QByteArray ba;
uint8_t const *begin = (uint8_t const *)out->data();
uint8_t const *end = begin + out->size();
uint8_t const *ptr = begin;
while (ptr < end) {
int mode = 0;
while (ptr < end) {
int c = *ptr & 0xff;
ptr++;
if (isdigit(c & 0xff)) {
mode = mode * 10 + (c - '0');
} else if (c == ' ') {
break;
}
}
uint8_t const *left = ptr;
while (ptr < end && *ptr) {
ptr++;
}
std::string name(left, ptr);
if (ptr + 20 < end) {
ptr++;
char tmp[100];
sprintf(tmp, "%06u %s ", mode, mode < 100000 ? "tree" : "blob");
char *p = tmp + 12;
for (int i = 0; i < 20; i++) {
sprintf(p, "%02x", ptr[i]);
p += 2;
}
ba.append(tmp, p - tmp);
ba.append('\t');
ba.append(name.c_str(), name.size());
ba.append('\n');
ptr += 20;
} else {
break;
}
}
*out = std::move(ba);
}
}
Git::Object::Type GitPack::stripHeader(QByteArray *out)
{
if (out) {
int n = out->size();
if (n > 0) {
char const *p = out->data();
if (n > 16) n = 16;
for (int i = 0; i < n; i++) {
if (p[i] == 0) {
Git::Object::Type type = Git::Object::Type::UNKNOWN;
if (strncmp(p, "blob ", 5) == 0) {
type = Git::Object::Type::BLOB;
} else if (strncmp(p, "tree ", 5) == 0) {
type = Git::Object::Type::TREE;
} else if (strncmp(p, "commit ", 7) == 0) {
type = Git::Object::Type::COMMIT;
} else if (strncmp(p, "tag ", 4) == 0) {
type = Git::Object::Type::TAG;
}
if (type != Git::Object::Type::UNKNOWN) {
*out = out->mid(i + 1);
}
return type;
}
}
}
}
return Git::Object::Type::UNKNOWN;
}
bool GitPack::decompress(QIODevice *in, size_t expanded_size, QByteArray *out, size_t *consumed, uint32_t *crc)
{
if (consumed) *consumed = 0;
try {
int err;
z_stream d_stream;
d_stream.zalloc = nullptr;
d_stream.zfree = nullptr;
d_stream.opaque = nullptr;
d_stream.next_in = nullptr;
d_stream.avail_in = 0;
err = inflateInit(&d_stream);
if (err != Z_OK) {
throw QString("failed: inflateInit");
}
while (1) {
if (expanded_size > 0 && (size_t)out->size() > expanded_size) {
throw QString("file too large");
}
uint8_t src[1024];
uint8_t tmp[65536];
if (d_stream.next_in != src && d_stream.avail_in > 0) {
memmove(src, d_stream.next_in, d_stream.avail_in);
}
d_stream.next_in = src;
if (d_stream.avail_in < sizeof(src)) {
int n = sizeof(src) - d_stream.avail_in;
n = in->read((char *)(src + d_stream.avail_in), n);
if (n >= 0) {
d_stream.avail_in += n;
}
}
d_stream.next_out = tmp;
size_t l = expanded_size - out->size();
if (l > sizeof(tmp)) l = sizeof(tmp);
d_stream.avail_out = l;
uLong total = d_stream.total_out;
err = ::inflate(&d_stream, Z_NO_FLUSH);
int in_len = (uint8_t *)d_stream.next_in - src;
if (consumed) *consumed += in_len;
if (crc) *crc = crc32(*crc, src, in_len);
int out_len = d_stream.total_out - total;
out->append((char const *)tmp, out_len);
if (err == Z_STREAM_END) {
break;
}
if (err != Z_OK) {
throw QString("failed: inflate");
}
}
err = inflateEnd(&d_stream);
if (err != Z_OK) {
throw QString("failed: inflateEnd");
}
return true;
} catch (QString const &e) {
qDebug() << e;
}
return false;
}
-bool GitPack::seekPackedObject(QIODevice *file, const GitPackIdxItem *item, Info *out)
+bool GitPack::seekPackedObject(QIODevice *file, GitPackIdxItem const *item, Info *out)
{
try {
Info info;
auto Read = [&](void *ptr, size_t len){
const auto l = file->read((char *)ptr, len);
if (l < 0 || ((size_t)(l)) != len) {
throw QString("failed to read");
}
info.checksum = crc32(info.checksum, (uint8_t const *)ptr, len);
};
file->seek(0);
uint32_t header[3];
Read(header, sizeof(int32_t) * 3);
if (memcmp(header, "PACK", 4) != 0) throw QString("invalid pack file");
uint32_t version = read_uint32_be(header + 1);
if (version < 2) throw QString("invalid pack file version");
/*int count = */read_uint32_be(header + 2);
file->seek(item->offset);
info.checksum = 0;
// cf. https://github.com/github/git-msysgit/blob/master/builtin/unpack-objects.c
{
size_t size = 0;
char c;
Read(&c, 1);
info.type = (Git::Object::Type)((c >> 4) & 7);
size = c & 0x0f;
int shift = 4;
while (c & 0x80) {
Read(&c, 1);
size |= (c & 0x7f) << shift;
shift += 7;
}
info.expanded_size = size;
}
if (info.type == Git::Object::Type::OFS_DELTA) {
uint64_t offset = 0;
char c;
Read(&c, 1);
offset = c & 0x7f;
while (c & 0x80) {
Read(&c, 1);
offset = ((offset + 1) << 7) | (c & 0x7f);
}
info.offset = offset;
} else if (info.type == Git::Object::Type::REF_DELTA) {
char bin[20];
Read(bin, 20);
char tmp[41];
for (int i = 0; i < 20; i++) {
sprintf(tmp + i * 2, "%02x", bin[i] & 0xff);
}
info.ref_id = QString::fromLatin1(tmp, GIT_ID_LENGTH);
}
*out = info;
return true;
} catch (QString const &e) {
qDebug() << e;
}
return false;
}
-bool GitPack::load(QIODevice *file, const GitPackIdxItem *item, Object *out)
+bool GitPack::load(QIODevice *file, GitPackIdxItem const *item, Object *out)
{
*out = Object();
try {
seekPackedObject(file, item, out);
if (decompress(file, out->expanded_size, &out->content, &out->packed_size, &out->checksum)) {
out->expanded_size = out->expanded_size;
return true;
}
} catch (QString const &e) {
qDebug() << e;
}
return false;
}
-bool GitPack::load(QString const &packfile, const GitPackIdxItem *item, GitPack::Object *out)
+bool GitPack::load(QString const &packfile, GitPackIdxItem const *item, GitPack::Object *out)
{
QFile file(packfile);
if (file.open(QFile::ReadOnly)) {
if (load(&file, item, out)) {
return true;
}
}
return false;
}
diff --git a/src/GitPack.h b/src/GitPack.h
index 42e5757..975ba0d 100644
--- a/src/GitPack.h
+++ b/src/GitPack.h
@@ -1,39 +1,39 @@
#ifndef GITPACK_H
#define GITPACK_H
#include <QIODevice>
#include <cstdint>
#include "Git.h"
struct GitPackIdxItem;
class GitPack {
public:
struct Info {
Git::Object::Type type = Git::Object::Type::UNKNOWN;
size_t expanded_size = 0;
uint64_t offset = 0;
QString ref_id;
uint32_t checksum = 0;
};
struct Object : public Info {
QByteArray content;
size_t packed_size = 0;
};
private:
static uint32_t read_uint32_be(const void *p)
{
auto const *q = (uint8_t const *)p;
return (q[0] << 24) | (q[1] << 16) | (q[2] << 8) | q[3];
}
public:
static bool decompress(QIODevice *in, size_t expanded_size, QByteArray *out, size_t *consumed = nullptr, uint32_t *crc = nullptr);
static bool load(QIODevice *file, GitPackIdxItem const *item, Object *out);
- static bool load(QString const &packfile, const GitPackIdxItem *item, Object *out);
- static bool seekPackedObject(QIODevice *file, const GitPackIdxItem *item, Info *out);
+ static bool load(QString const &packfile, GitPackIdxItem const *item, Object *out);
+ static bool seekPackedObject(QIODevice *file, GitPackIdxItem const *item, Info *out);
static void decodeTree(QByteArray *out);
static Git::Object::Type stripHeader(QByteArray *out);
};
#endif // GITPACK_H
diff --git a/src/GitPackIdxV2.cpp b/src/GitPackIdxV2.cpp
index 88c3d8e..7a274f5 100644
--- a/src/GitPackIdxV2.cpp
+++ b/src/GitPackIdxV2.cpp
@@ -1,129 +1,129 @@
#include "GitPackIdxV2.h"
#include <QCryptographicHash>
#include <QDebug>
#include <QFile>
QString GitPackIdxV2::toString(uint8_t const *p)
{
char tmp[41];
for (int i = 0; i < GIT_ID_LENGTH / 2; i++) {
sprintf(tmp + i * 2, "%02x", p[i]);
}
return QString::fromLatin1(tmp, GIT_ID_LENGTH);
}
uint32_t GitPackIdxV2::read_uint32_be(const void *p)
{
auto const *q = (uint8_t const *)p;
return (q[0] << 24) | (q[1] << 16) | (q[2] << 8) | q[3];
}
uint32_t GitPackIdxV2::get_fanout(const GitPackIdxV2::header_t *t, int i)
{
return read_uint32_be(&t->fanout[i]);
}
const uint8_t *GitPackIdxV2::object(int i) const
{
return d.objects[i].id;
}
uint32_t GitPackIdxV2::offset(int i) const
{
return read_uint32_be(&d.offsets[i]);
}
uint32_t GitPackIdxV2::checksum(int i) const
{
return read_uint32_be(&d.checksums[i]);
}
void GitPackIdxV2::clear()
{
d = Data();
}
bool GitPackIdxV2::parse(QIODevice *in)
{
try {
auto Read = [&](void *ptr, size_t len){
const auto l = in->read((char *)ptr, len);
return 0 < l && static_cast<size_t>(l) == len;
};
static char const magic[] = "\xff\x74\x4f\x63\x00\x00\x00\x02";
if (!Read(&d.header, sizeof(d.header))) throw QString("failed to read the idx header");
if (memcmp(d.header.magic, magic, 8) != 0) throw QString("invalid idx header");
uint32_t size = get_fanout(&d.header, 255);
if (size > 1000000) throw QString("number of objects in the idx file is too big");
size_t size4 = size * sizeof(uint32_t);
d.objects.resize(size);
d.checksums.resize(size);
d.offsets.resize(size);
if (!Read(&d.objects[0], size * 20)) throw QString("failed to read the objects");
if (!Read(&d.checksums[0], size4)) throw QString("failed to read the checksums");
if (!Read(&d.offsets[0], size4)) throw QString("failed to read the offsets");
if (!Read(&d.trailer, sizeof(d.trailer))) throw QString("failed to read the trailer");
QCryptographicHash sha1(QCryptographicHash::Sha1);
sha1.addData((char const *)&d.header, sizeof(d.header));
sha1.addData((char const *)&d.objects[0], size * 20);
sha1.addData((char const *)&d.checksums[0], size4);
sha1.addData((char const *)&d.offsets[0], size4);
sha1.addData((char const *)&d.trailer.packfile_checksum, sizeof(d.trailer) - 20);
QByteArray chksum = sha1.result();
Q_ASSERT(chksum.size() == 20);
if (memcmp(chksum.data(), d.trailer.idxfile_checksum, 20) != 0) {
throw QString("idx checksum is not correct");
}
for (size_t i = 0; i < size; i++) {
GitPackIdxItem item;
item.id = toString(object(i));
item.offset = offset(i);
item.checksum = checksum(i);
d.item_list.push_back(item);
}
for (size_t i = 0; i < size; i++) {
GitPackIdxItem &item = d.item_list[i];
if (i + 1 < size) {
item.packed_size = d.item_list[i + 1].offset - d.item_list[i].offset;
}
}
return true;
} catch (QString const &e) {
qDebug() << e;
}
return false;
}
bool GitPackIdxV2::parse(QString const &idxfile)
{
clear();
QFile file(idxfile);
if (file.open(QFile::ReadOnly)) {
if (parse(&file)) {
return true;
}
}
return false;
}
-const GitPackIdxItem *GitPackIdxV2::item(QString const &id) const
+GitPackIdxItem const *GitPackIdxV2::item(QString const &id) const
{
for (const auto & i : d.item_list) {
if (i.id == id) {
return &i;
}
}
return nullptr;
}
-const GitPackIdxItem *GitPackIdxV2::item(size_t offset) const
+GitPackIdxItem const *GitPackIdxV2::item(size_t offset) const
{
for (GitPackIdxItem const &item : d.item_list) {
if (item.offset == offset) return &item;
}
return nullptr;
}
diff --git a/src/LogTableWidget.cpp b/src/LogTableWidget.cpp
index 8188930..5f28fb4 100644
--- a/src/LogTableWidget.cpp
+++ b/src/LogTableWidget.cpp
@@ -1,334 +1,334 @@
#include "LogTableWidget.h"
#include <QDebug>
#include <QEvent>
#include <QPainter>
#include <QProxyStyle>
#include <cmath>
#include "MainWindow.h"
#include <QApplication>
#include "MyTableWidgetDelegate.h"
#include "common/misc.h"
struct LogTableWidget::Private {
};
class LogTableWidgetDelegate : public MyTableWidgetDelegate {
private:
MainWindow *mainwindow() const
{
auto *w = dynamic_cast<LogTableWidget *>(QStyledItemDelegate::parent());
Q_ASSERT(w);
return w->mainwindow();
}
static QColor labelColor(int kind)
{
switch (kind) {
case BasicMainWindow::Label::Head: return QColor(255, 192, 224); // blue
case BasicMainWindow::Label::LocalBranch: return QColor(192, 224, 255); // blue
case BasicMainWindow::Label::RemoteBranch: return QColor(192, 240, 224); // green
case BasicMainWindow::Label::Tag: return QColor(255, 224, 192); // orange
}
return QColor(224, 224, 224); // gray
}
static QColor hiliteColor(QColor const &color)
{
int r = color.red();
int g = color.green();
int b = color.blue();
r = 255 - (255 - r) / 2;
g = 255 - (255 - g) / 2;
b = 255 - (255 - b) / 2;
return QColor(r, g, b);
}
static QColor shadowColor(QColor const &color)
{
return QColor(color.red() / 2, color.green() / 2, color.blue() / 2);
}
- void drawSignatureIcon(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const
+ void drawSignatureIcon(QPainter *painter, const QStyleOptionViewItem &opt, QModelIndex const &index) const
{
if (!opt.widget->isEnabled()) return;
Git::CommitItem const *commit = mainwindow()->commitItem(index.row());
QIcon icon = mainwindow()->verifiedIcon(commit->signature);
if (!icon.isNull()) {
QRect r = opt.rect.adjusted(6, 3, 0, -3);
int h = r.height();
int w = h;
int x = r.x() + r.width() - w;
int y = r.y();
icon.paint(painter, x, y, w, h);
}
}
- void drawAvatar(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const
+ void drawAvatar(QPainter *painter, const QStyleOptionViewItem &opt, QModelIndex const &index) const
{
if (!opt.widget->isEnabled()) return;
int row = index.row();
QIcon icon = mainwindow()->committerIcon(row);
if (!icon.isNull()) {
int h = opt.rect.height();
int w = h;
int x = opt.rect.x() + opt.rect.width() - w;
int y = opt.rect.y();
painter->save();
painter->setOpacity(0.5); // 半透明で描画
icon.paint(painter, x, y, w, h);
painter->restore();
}
}
- void drawDescription(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const
+ void drawDescription(QPainter *painter, const QStyleOptionViewItem &opt, QModelIndex const &index) const
{
int row = index.row();
QList<BasicMainWindow::Label> const *labels = mainwindow()->label(row);
if (labels) {
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
QFontMetrics fm = painter->fontMetrics();
const int space = 8;
int x = opt.rect.x() + opt.rect.width() - 3;
int x1 = x;
int y0 = opt.rect.y();
int y1 = y0 + opt.rect.height() - 1;
int i = labels->size();
while (i > 0) {
i--;
BasicMainWindow::Label const &label = labels->at(i);
QString text = misc::abbrevBranchName(label.text);
int w = fm.size(0, text).width() + space * 2;
int x0 = x1 - w;
QRect r(x0, y0, x1 - x0, y1 - y0);
painter->setPen(Qt::NoPen);
auto DrawRect = [&](int dx, int dy, QColor color){
painter->setBrush(color);
painter->drawRoundedRect(r.adjusted(lround(dx + 3), lround(dy + 3), lround(dx - 3), lround(dy - 3)), 3, 3);
};
QColor color = labelColor(label.kind);
QColor hilite = hiliteColor(color);
QColor shadow = shadowColor(color);
DrawRect(-1, -1, hilite);
DrawRect(1, 1, shadow);
DrawRect(0, 0, color);
painter->setPen(Qt::black);
painter->setBrush(Qt::NoBrush);
qApp->style()->drawItemText(painter, r.adjusted(space, 0, 0, 0), opt.displayAlignment, opt.palette, true, text);
x1 = x0;
}
painter->restore();
}
}
public:
explicit LogTableWidgetDelegate(QObject *parent = Q_NULLPTR)
: MyTableWidgetDelegate(parent)
{
}
- void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, QModelIndex const &index) const override
{
MyTableWidgetDelegate::paint(painter, option, index);
// signatureの描画
if (index.column() == 1) {
drawSignatureIcon(painter, option, index);
}
// avatarの描画
if (index.column() == 3) {
drawAvatar(painter, option, index);
}
// Descriptionの描画
if (index.column() == 4) {
drawDescription(painter, option, index);
}
}
};
LogTableWidget::LogTableWidget(QWidget *parent)
: QTableWidget(parent)
, m(new Private)
{
setItemDelegate(new LogTableWidgetDelegate(this));
}
LogTableWidget::~LogTableWidget()
{
delete m;
}
MainWindow *LogTableWidget::mainwindow()
{
MainWindow *mw = qobject_cast<MainWindow *>(window());
Q_ASSERT(mw);
return mw;
}
void drawBranch(QPainterPath *path, double x0, double y0, double x1, double y1, double r, bool bend_early)
{
const double k = 0.55228475;
if (x0 == x1) {
path->moveTo(x0, y0);
path->lineTo(x1, y1);
} else {
double ym = bend_early ? (y0 + r) : (y1 - r);
double h = fabs(y1 - y0);
double w = fabs(x1 - x0);
if (r > h / 2) r = h / 2;
if (r > w / 2) r = w / 2;
double s = r;
if (x0 > x1) r = -r;
if (y0 > y1) s = -s;
if (0) {
path->moveTo(x0, y0);
path->lineTo(x0, ym - s);
path->cubicTo(x0, ym - s + s * k, x0 + r - r * k, ym, x0 + r, ym);
path->lineTo(x1 - r, ym);
path->cubicTo(x1 - r + r * k, ym, x1, ym + s - s * k, x1, ym + s);
path->lineTo(x1, y1);
} else {
if (bend_early) {
path->moveTo(x0, y0);
path->cubicTo(x0, ym, x1, ym, x1, ym + ym - y0);
path->lineTo(x1, y1);
} else {
path->moveTo(x0, y0);
path->lineTo(x0, ym + ym - y1);
path->cubicTo(x0, ym, x1, ym, x1, y1);
}
}
}
}
void LogTableWidget::paintEvent(QPaintEvent *e)
{
if (rowCount() < 1) return;
QTableWidget::paintEvent(e);
QPainter pr(viewport());
pr.setRenderHint(QPainter::Antialiasing);
pr.setBrush(QBrush(QColor(255, 255, 255)));
Git::CommitItemList const *list = &mainwindow()->getLogs();
int indent_span = 16;
auto ItemRect = [&](int row){
QRect r;
QTableWidgetItem *p = item(row, 0);
if (p) {
r = visualItemRect(p);
}
return r;
};
int line_width = 2;
auto ItemPoint = [&](int depth, QRect const &rect){
int h = rect.height();
double n = h / 2.0;
double x = floor(rect.x() + n + depth * indent_span);
double y = floor(rect.y() + n);
return QPointF(x, y);
};
auto SetPen = [&](QPainter *pr, int level, bool /*continued*/){
QColor c = mainwindow()->color(level + 1);
Qt::PenStyle s = Qt::SolidLine;
pr->setPen(QPen(c, line_width, s));
};
auto DrawLine = [&](size_t index, int itemrow){
QRect rc1;
if (index < list->size()) {
Git::CommitItem const &item1 = list->at(index);
rc1 = ItemRect(itemrow);
QPointF pt1 = ItemPoint(item1.marker_depth, rc1);
double halfheight = rc1.height() / 2.0;
for (TreeLine const &line : item1.parent_lines) {
if (line.depth >= 0) {
QPainterPath *path = nullptr;
Git::CommitItem const &item2 = list->at(line.index);
QRect rc2 = ItemRect(line.index);
if (index + 1 == (size_t)line.index || line.depth == item1.marker_depth || line.depth == item2.marker_depth) {
QPointF pt2 = ItemPoint(line.depth, rc2);
if (pt2.y() > 0) {
path = new QPainterPath();
drawBranch(path, pt1.x(), pt1.y(), pt2.x(), pt2.y(), halfheight, line.bend_early);
}
} else {
QPointF pt3 = ItemPoint(item2.marker_depth, rc2);
if (pt3.y() > 0) {
path = new QPainterPath();
QRect rc3 = ItemRect(itemrow + 1);
QPointF pt2 = ItemPoint(line.depth, rc3);
drawBranch(path, pt1.x(), pt1.y(), pt2.x(), pt2.y(), halfheight, true);
drawBranch(path, pt2.x(), pt2.y(), pt3.x(), pt3.y(), halfheight, false);
}
}
if (path) {
SetPen(&pr, line.color_number, false);
pr.drawPath(*path);
delete path;
}
}
}
}
return rc1.y();
};
auto DrawMark = [&](size_t index, int row){
double x, y;
y = 0;
if (index < list->size()) {
Git::CommitItem const &item = list->at(index);
QRect rc = ItemRect(row);
QPointF pt = ItemPoint(item.marker_depth, rc);
double r = 4;
x = pt.x() - r;
y = pt.y() - r;
SetPen(&pr, item.marker_depth, false);
if (item.resolved) {
// ◯
pr.drawEllipse((int)x, (int)y, int(r * 2), int(r * 2));
} else {
// ▽
QPainterPath path;
path.moveTo(pt.x(), pt.y() + r);
path.lineTo(pt.x() - r, pt.y() - r);
path.lineTo(pt.x() + r, pt.y() - r);
path.lineTo(pt.x(), pt.y() + r);
pr.drawPath(path);
}
}
return y;
};
// draw lines
pr.setOpacity(0.5);
pr.setBrush(Qt::NoBrush);
for (size_t i = 0; i < list->size(); i++) {
double y = DrawLine(i, i);
if (y >= height()) break;
}
// draw marks
pr.setOpacity(1);
pr.setBrush(mainwindow()->color(0));
for (size_t i = 0; i < list->size(); i++) {
double y = DrawMark(i, i);
if (y >= height()) break;
}
}
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 78b7abb..268e7da 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -1,2816 +1,2822 @@
#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 "RebaseOntoDialog.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 "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 {
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, SIGNAL(signalWriteLog(QByteArray)), this, SLOT(writeLog(QByteArray)));
connect(ui->dockWidget_log, SIGNAL(visibilityChanged(bool)), this, SLOT(onLogVisibilityChanged()));
connect(ui->widget_log, SIGNAL(idle()), this, SLOT(onLogIdle()));
connect(ui->treeWidget_repos, SIGNAL(dropped()), this, SLOT(onRepositoriesTreeDropped()));
- connect((AbstractPtyProcess *)getPtyProcess(), SIGNAL(completed()), this, SLOT(onPtyProcessCompleted()));
+ connect((AbstractPtyProcess *)getPtyProcess(), SIGNAL(completed(QVariant)), this, SLOT(onPtyProcessCompleted(QVariant)));
connect(this, &BasicMainWindow::remoteInfoChanged, [&](){
ui->lineEdit_remote->setText(currentRemoteName());
});
// リモート監視
connect(this, SIGNAL(signalCheckRemoteUpdate()), &m->remote_watcher, SLOT(checkRemoteUpdate()));
connect(&m->remote_watcher_timer, SIGNAL(timeout()), &m->remote_watcher, SLOT(checkRemoteUpdate()));
connect(this, &MainWindow::updateButton, [&](){
doUpdateButton();
});
m->remote_watcher.start(this);
setWatchRemoteInterval(appsettings()->watch_remote_changes_every_mins);
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(webContext());
connect(getAvatarLoader(), SIGNAL(updated()), this, SLOT(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);
}
}
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 QMainWindow::event(event);
+ return BasicMainWindow::event(event);
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
QEvent::Type et = event->type();
if (et == QEvent::KeyPress) {
auto *e = dynamic_cast<QKeyEvent *>(event);
Q_ASSERT(e);
int k = e->key();
if (k == Qt::Key_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) {
// ファイルリストがフォーカスを得たとき、diffビューを更新する。(コンテキストメニュー対応)
if (watched == ui->listWidget_unstaged) {
m->last_focused_file_list = watched;
updateStatusBarText();
updateUnstagedFileCurrentItem();
return true;
}
if (watched == ui->listWidget_staged) {
m->last_focused_file_list = watched;
updateStatusBarText();
updateStagedFileCurrentItem();
return true;
}
if (watched == ui->listWidget_files) {
m->last_focused_file_list = watched;
}
}
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 MainWindow::fetch(GitPtr g)
{
setPtyCondition(PtyCondition::Fetch);
setPtyProcessOk(true);
g->fetch(getPtyProcess());
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)) {
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);
});
}
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).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::updateCommitGraph()
{
auto const &logs = getLogs();
auto *logsp = getLogsPtr();
const size_t LogCount = logs.size();
// 樹形図情報を構築する
if (LogCount > 0) {
auto LogItem = [&](size_t i)->Git::CommitItem &{ return logsp->at(i); };
enum { // 有向グラフを構築するあいだ CommitItem::marker_depth をフラグとして使用する
UNKNOWN = 0,
KNOWN = 1,
};
for (Git::CommitItem &item : *logsp) {
item.marker_depth = UNKNOWN;
}
// コミットハッシュを検索して、親コミットのインデックスを求める
for (size_t i = 0; i < LogCount; i++) {
Git::CommitItem *item = &LogItem(i);
item->parent_lines.clear();
if (item->parent_ids.empty()) {
item->resolved = true;
} else {
for (int j = 0; j < item->parent_ids.size(); j++) { // 親の数だけループ
QString const &parent_id = item->parent_ids[j]; // 親のハッシュ値
for (size_t k = i + 1; k < LogCount; k++) { // 親を探す
if (LogItem(k).commit_id == parent_id) { // ハッシュ値が一致したらそれが親
item->parent_lines.emplace_back(k); // インデックス値を記憶
LogItem(k).has_child = true;
LogItem(k).marker_depth = KNOWN;
item->resolved = true;
break;
}
}
}
}
}
std::vector<Element> elements; // 線分リスト
{ // 線分リストを作成する
std::deque<Task> tasks; // 未処理タスクリスト
{
for (size_t i = 0; i < LogCount; i++) {
Git::CommitItem *item = &LogItem(i);
if (item->marker_depth == UNKNOWN) {
int n = item->parent_lines.size(); // 最初のコミットアイテム
for (int j = 0; j < n; j++) {
tasks.emplace_back(i, j); // タスクを追加
}
}
item->marker_depth = UNKNOWN;
}
}
while (!tasks.empty()) { // タスクが残っているならループ
Element e;
Task task;
{ // 最初のタスクを取り出す
task = tasks.front();
tasks.pop_front();
}
e.indexes.push_back(task.index); // 先頭のインデックスを追加
int index = LogItem(task.index).parent_lines[task.parent].index; // 開始インデックス
while (index > 0 && (size_t)index < LogCount) { // 最後に到達するまでループ
e.indexes.push_back(index); // インデックスを追加
int n = LogItem(index).parent_lines.size(); // 親の数
if (n == 0) break; // 親がないなら終了
Git::CommitItem *item = &LogItem(index);
if (item->marker_depth == KNOWN) break; // 既知のアイテムに到達したら終了
item->marker_depth = KNOWN; // 既知のアイテムにする
for (int i = 1; i < n; i++) {
tasks.emplace_back(index, i); // タスク追加
}
index = LogItem(index).parent_lines[0].index; // 次の親(親リストの先頭の要素)
}
if (e.indexes.size() >= 2) {
elements.push_back(e);
}
}
}
// 線情報をクリア
for (Git::CommitItem &item : *logsp) {
item.marker_depth = -1;
item.parent_lines.clear();
}
// マークと線の深さを決める
if (!elements.empty()) {
{ // 優先順位を調整する
std::sort(elements.begin(), elements.end(), [](Element const &left, Element const &right){
int i = 0;
{ // 長いものを優先して左へ
int l = left.indexes.back() - left.indexes.front();
int r = right.indexes.back() - right.indexes.front();
i = r - l; // 降順
}
if (i == 0) {
// コミットが新しいものを優先して左へ
int l = left.indexes.front();
int r = right.indexes.front();
i = l - r; // 昇順
}
return i < 0;
});
// 子の無いブランチ(タグ等)が複数連続しているとき、古いコミットを右に寄せる
{
for (size_t i = 0; i + 1 < elements.size(); i++) {
Element *e = &elements[i];
int index1 = e->indexes.front();
if (index1 > 0 && !LogItem(index1).has_child) { // 子がない
// 新しいコミットを探す
for (size_t j = i + 1; j < elements.size(); j++) { // 現在位置より後ろを探す
Element *f = &elements[j];
int index2 = f->indexes.front();
if (index1 == index2 + 1) { // 一つだけ新しいコミット
Element t = std::move(*f);
elements.erase(elements.begin() + j); // 移動元を削除
elements.insert(elements.begin() + i, std::move(t)); // 現在位置に挿入
}
}
// 古いコミットを探す
size_t j = 0;
while (j < i) { // 現在位置より前を探す
Element *f = &elements[j];
int index2 = f->indexes.front();
if (index1 + 1 == index2) { // 一つだけ古いコミット
Element t = std::move(*f);
elements.erase(elements.begin() + j); // 移動元を削除
elements.insert(elements.begin() + i, std::move(t)); // 現在位置の次に挿入
index1 = index2;
f = e;
} else {
j++;
}
}
}
}
}
}
{ // 最初の線は深さを0にする
Element *e = &elements.front();
for (size_t i = 0; i < e->indexes.size(); i++) {
int index = e->indexes[i];
LogItem(index).marker_depth = 0; // マークの深さを設定
e->depth = 0; // 線の深さを設定
}
}
// 最初以外の線分の深さを決める
for (size_t i = 1; i < elements.size(); i++) { // 最初以外をループ
Element *e = &elements[i];
int depth = 1;
while (1) { // 失敗したら繰り返し
for (size_t j = 0; j < i; j++) { // 既に処理済みの線を調べる
Element const *f = &elements[j]; // 検査対象
if (e->indexes.size() == 2) { // 二つしかない場合
int from = e->indexes[0]; // 始点
int to = e->indexes[1]; // 終点
if (LogItem(from).has_child) {
for (size_t k = 0; k + 1 < f->indexes.size(); k++) { // 検査対象の全ての線分を調べる
int curr = f->indexes[k];
int next = f->indexes[k + 1];
if (from > curr && to == next) { // 決定済みの線に直結できるか判定
e->indexes.back() = from + 1; // 現在の一行下に直結する
e->depth = elements[j].depth; // 接続先の深さ
goto DONE; // 決定
}
}
}
}
if (depth == f->depth) { // 同じ深さ
if (e->indexes.back() > f->indexes.front() && e->indexes.front() < f->indexes.back()) { // 重なっている
goto FAIL; // この深さには線を置けないのでやりなおし
}
}
}
for (size_t j = 0; j < e->indexes.size(); j++) {
int index = e->indexes[j];
Git::CommitItem *item = &LogItem(index);
if (j == 0 && item->has_child) { // 最初のポイントで子がある場合
// nop
} else if ((j > 0 && j + 1 < e->indexes.size()) || item->marker_depth < 0) { // 最初と最後以外、または、未確定の場合
item->marker_depth = depth; // マークの深さを設定
}
}
e->depth = depth; // 深さを決定
goto DONE; // 決定
FAIL:;
depth++; // 一段深くして再挑戦
}
DONE:;
}
// 線情報を生成する
for (const auto & e : elements) {
auto ColorNumber = [&](){ return e.depth; };
size_t count = e.indexes.size();
for (size_t j = 0; j + 1 < count; j++) {
int curr = e.indexes[j];
int next = e.indexes[j + 1];
TreeLine line(next, e.depth);
line.color_number = ColorNumber();
line.bend_early = (j + 2 < count || !LogItem(next).resolved);
if (j + 2 == count) {
if (count > 2 || !LogItem(curr).has_child) { // 直結ではない、または、子がない
line.depth = LogItem(next).marker_depth; // 合流する先のアイテムの深さと同じにする
}
}
LogItem(curr).parent_lines.push_back(line); // 線を追加
}
}
} else {
if (LogCount == 1) { // コミットが一つだけ
LogItem(0).marker_depth = 0;
}
}
}
}
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::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 (qApp->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);
setWatchRemoteInterval(appsettings()->watch_remote_changes_every_mins);
}
}
-void MainWindow::onCloneCompleted(bool success)
+void MainWindow::onCloneCompleted(bool success, QVariant &userdata)
{
if (success) {
- saveRepositoryBookmark(getTempRepoForCloneComplete());
- setCurrentRepository(getTempRepoForCloneComplete(), false);
+ RepositoryItem r = userdata.value<RepositoryItem>();
+ saveRepositoryBookmark(r);
+ setCurrentRepository(r, false);
openRepository(true);
}
}
-void MainWindow::onPtyProcessCompleted()
+void MainWindow::onPtyProcessCompleted(QVariant userdata)
{
switch (getPtyCondition()) {
case PtyCondition::Clone:
- onCloneCompleted(getPtyProcessOk());
+ 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_tag_push_all_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);
JumpDialog::sort(&items);
{
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());
}
bool MainWindow::isRemoteOnline() const
{
return ui->radioButton_remote_online->isChecked();
}
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_pull->setEnabled(f);
ui->action_push->setEnabled(f);
ui->toolButton_fetch->setEnabled(f);
ui->toolButton_pull->setEnabled(f);
ui->toolButton_push->setEnabled(f);
}
void MainWindow::setRemoteOnline(bool f)
{
QRadioButton *rb = nullptr;
rb = f ? ui->radioButton_remote_online : ui->radioButton_remote_offline;
rb->blockSignals(true);
rb->click();
rb->blockSignals(false);
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);
}
QStringList MainWindow::remoteBranches(QString const &id)
{
QStringList list;
GitPtr g = git();
if (isValidWorkingCopy(g)) {
NamedCommitList named_commits = namedCommitItems(Branches | Remotes);
JumpDialog::sort(&named_commits);
for (NamedCommitItem const &item : named_commits) {
if (item.id == id && !item.remote.isEmpty()) {
list.push_back(item.remote / item.name);
}
}
}
return list;
}
void MainWindow::deleteRemoteBranch(Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QStringList remote_branches = remoteBranches(commit->commit_id);
if (remote_branches.isEmpty()) return;
DeleteBranchDialog dlg(this, true, QStringList(), remote_branches);
if (dlg.exec() == QDialog::Accepted) {
setLogEnabled(g, true);
QStringList names = dlg.selectedBranchNames();
for (QString const &name : names) {
int i = name.indexOf('/');
if (i > 0) {
QString remote = name.mid(0, i);
QString branch = ':' + name.mid(i + 1);
pushSetUpstream(remote, branch);
}
}
}
}
void MainWindow::on_action_delete_remote_branch_triggered()
{
deleteRemoteBranch(selectedCommitItem());
}
void MainWindow::rebaseOnto()
{
struct Commit {
QStringList parents;
QStringList children;
};
std::map<QString, Commit> commit_map;
Git::CommitItem const *commit = selectedCommitItem();
if (commit) {
auto const &logs = getLogs();
for (Git::CommitItem const &item : logs) {
Commit c;
c.parents = item.parent_ids;
commit_map[item.commit_id] = c;
}
for (Git::CommitItem const &item : logs) {
for (QString const &parent : item.parent_ids) {
auto it = commit_map.find(parent);
if (it != commit_map.end()) {
it->second.children.push_back(item.commit_id);
}
}
}
QString upstream;
QString id = getHeadId();
while (1) {
auto it = commit_map.find(id);
if (it == commit_map.end()) break;
Commit const &c = it->second;
if (c.parents.size() > 1) goto done;
if (c.parents.size() != 1) break;
if (id != getHeadId()) {
if (c.children.size() > 1) goto done;
if (!findTag(id).isEmpty()) goto done;
auto it2 = branchMapRef().find(id);
if (it2 != branchMapRef().end()) {
QList<Git::Branch> b = it2->second;
if (!it2->second.empty()) goto done;
}
}
id = c.parents.first();
continue;
done:;
upstream = id;
break;
}
QString newbase = commit->commit_id;
QString branch = getHeadId();
RebaseOntoDialog dlg(this);
if (dlg.exec(newbase, upstream, branch) == QDialog::Accepted) {
reopenRepository(true, [&](GitPtr g){
setPtyCondition(PtyCondition::Pull);
setPtyProcessOk(true);
g->rebaseOnto(newbase, upstream, branch, getPtyProcess());
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
});
}
}
}
void MainWindow::on_action_rebase_onto_triggered()
{
rebaseOnto();
}
void MainWindow::on_action_test_triggered()
{
}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index d93870a..52d285b 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -1,193 +1,194 @@
#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);
private:
Ui::MainWindow *ui;
void updateFilesList(QString id, bool wait) override;
- void updateFilesList(const Git::CommitItem &commit, bool wait);
+ 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(const Git::CommitItem *commit);
- void mergeBranch(const Git::CommitItem *commit);
- void rebaseBranch(const Git::CommitItem *commit);
+ 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);
bool isRemoteOnline() const override;
void startTimers();
- void onCloneCompleted(bool success);
+ void onCloneCompleted(bool success, QVariant &userdata);
bool fetch(GitPtr g);
void setNetworkingCommandsEnabled(bool f);
void blame(QListWidgetItem *item);
void blame();
QListWidgetItem *currentFileItem() const;
void execAreYouSureYouWantToContinueConnectingDialog();
- void deleteRemoteBranch(const Git::CommitItem *commit);
+ void deleteRemoteBranch(Git::CommitItem const *commit);
QStringList remoteBranches(QString const &id);
void rebaseOnto();
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);
private slots:
void doUpdateButton();
void onLogVisibilityChanged();
- void onPtyProcessCompleted();
+ void onPtyProcessCompleted(QVariant userdata);
void onRepositoriesTreeDropped();
void on_action_about_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_open_existing_working_copy_triggered();
void on_action_pull_triggered();
void on_action_push_triggered();
void on_action_rebase_onto_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_set_config_user_triggered();
void on_action_set_gpg_signing_triggered();
void on_action_stop_process_triggered();
void on_action_tag_push_all_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_push_u_triggered();
protected:
void closeEvent(QCloseEvent *event) override;
void internalWriteLog(const char *ptr, int len) override;
- const RepositoryItem *selectedRepositoryItem() const override;
+ RepositoryItem const *selectedRepositoryItem() const override;
void removeSelectedRepositoryFromBookmark(bool ask) override;
protected slots:
void onLogIdle();
signals:
void signalSetRemoteChanged(bool f);
void onEscapeKeyPressed();
void updateButton();
};
#endif // MAINWINDOW_H
diff --git a/src/MyProcess.h b/src/MyProcess.h
index a3f996b..1d3d37d 100644
--- a/src/MyProcess.h
+++ b/src/MyProcess.h
@@ -1,24 +1,24 @@
#ifndef MYPROCESS_H
#define MYPROCESS_H
#include <QtGlobal>
#ifdef Q_OS_WIN
#include "win32/Win32Process.h"
#include "win32/Win32PtyProcess.h"
using Process = Win32Process;
using PtyProcess = Win32PtyProcess;
#else
#include "unix/UnixProcess.h"
#include "unix/UnixPtyProcess.h"
using Process = UnixProcess;
using PtyProcess = UnixPtyProcess;
#endif
class misc2 {
public:
static int runCommand(QString const &cmd, QByteArray *out);
- static int runCommand(QString const &cmd, const QByteArray *in, QByteArray *out);
+ static int runCommand(QString const &cmd, QByteArray const *in, QByteArray *out);
};
#endif // MYPROCESS_H
diff --git a/src/MyTableWidgetDelegate.cpp b/src/MyTableWidgetDelegate.cpp
index 9e4617a..b0074e3 100644
--- a/src/MyTableWidgetDelegate.cpp
+++ b/src/MyTableWidgetDelegate.cpp
@@ -1,36 +1,36 @@
#include "MyTableWidgetDelegate.h"
#include <QPainter>
#include <QTableWidget>
#include <QApplication>
MyTableWidgetDelegate::MyTableWidgetDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
-void MyTableWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+void MyTableWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, QModelIndex const &index) const
{
QStyleOptionViewItem opt = option;
#ifdef Q_OS_WIN
// 選択枠を描画
if (option.showDecorationSelected) {
QTableWidget const *tablewidget = qobject_cast<QTableWidget const *>(option.widget);
Q_ASSERT(tablewidget);
int w = tablewidget->viewport()->rect().width();
painter->save();
QStyleOptionViewItem o = option;
painter->setClipRect(o.rect);
o.rect = QRect(1, o.rect.y(), w - 2, o.rect.height());
qApp->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &o, painter, nullptr);
painter->restore();
}
opt.state &= ~QStyle::State_Selected; // 行の選択枠は描画しない
#endif
opt.state &= ~QStyle::State_HasFocus; // セルのフォーカス枠は描画しない
QStyledItemDelegate::paint(painter, opt, index); // デフォルトの描画
}
diff --git a/src/MyTableWidgetDelegate.h b/src/MyTableWidgetDelegate.h
index 5c963e8..be770ec 100644
--- a/src/MyTableWidgetDelegate.h
+++ b/src/MyTableWidgetDelegate.h
@@ -1,13 +1,13 @@
#ifndef MYTABLEWIDGETDELEGATE_H
#define MYTABLEWIDGETDELEGATE_H
#include <QStyledItemDelegate>
class MyTableWidgetDelegate : public QStyledItemDelegate {
public:
explicit MyTableWidgetDelegate(QObject *parent = nullptr);
- void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, QModelIndex const &index) const override;
};
#endif // MYTABLEWIDGETDELEGATE_H
diff --git a/src/RemoteWatcher.cpp b/src/RemoteWatcher.cpp
index fc46d91..9696269 100644
--- a/src/RemoteWatcher.cpp
+++ b/src/RemoteWatcher.cpp
@@ -1,51 +1,51 @@
#include "RemoteWatcher.h"
#include "common/joinpath.h"
#include <QFileInfo>
void RemoteWatcher::start(MainWindow *mw)
{
mainwindow_ = mw;
QThread::start();
moveToThread(this);
}
void RemoteWatcher::setCurrent(QString const &remote, QString const &branch)
{
remote_ = remote;
branch_ = branch;
}
void RemoteWatcher::checkRemoteUpdate()
{
QString dir = mainwindow()->currentWorkingCopyDir();
QString refs;
QString local_id;
if (QFileInfo(dir).isDir()) {
refs = "refs/remotes" / remote_ / branch_;
QString head = dir / ".git" / refs;
QFile file1(head);
if (file1.open(QFile::ReadOnly)) {
local_id = file1.readLine().trimmed();
}
refs = "refs/heads" / branch_;
}
if (local_id.isEmpty()) return;
QString remote_id;
GitPtr g = mainwindow()->git();
auto list = g->ls_remote();
for (auto item : list) {
if (item.name == refs) {
remote_id = item.commit_id;
break;
}
}
bool changed = remote_id != local_id;
- emit mainwindow()->signalSetRemoteChanged(changed);
+ mainwindow()->notifyRemoteChanged(changed);
}
diff --git a/src/RepositoryData.h b/src/RepositoryData.h
index 799f330..e6736c0 100644
--- a/src/RepositoryData.h
+++ b/src/RepositoryData.h
@@ -1,24 +1,26 @@
#ifndef REPOSITORYDATA_H
#define REPOSITORYDATA_H
#include <QList>
+#include <QMetaType>
enum class ServerType {
Standard,
GitHub,
};
struct RepositoryItem {
QString name;
QString group;
QString local_dir;
};
+Q_DECLARE_METATYPE(RepositoryItem);
class RepositoryBookmark {
public:
RepositoryBookmark() = default;
static bool save(QString const &path, QList<RepositoryItem> const *items);
static QList<RepositoryItem> load(QString const &path);
};
#endif // REPOSITORYDATA_H
diff --git a/src/RepositoryPropertyDialog.cpp b/src/RepositoryPropertyDialog.cpp
index 64dd8e9..caad3d4 100644
--- a/src/RepositoryPropertyDialog.cpp
+++ b/src/RepositoryPropertyDialog.cpp
@@ -1,141 +1,141 @@
#include "RepositoryPropertyDialog.h"
#include "ui_RepositoryPropertyDialog.h"
#include "common/misc.h"
#include "MainWindow.h"
#include <QClipboard>
#include <QMenu>
#include <QMessageBox>
-RepositoryPropertyDialog::RepositoryPropertyDialog(BasicMainWindow *parent, GitPtr g, const RepositoryItem &item, bool open_repository_menu)
+RepositoryPropertyDialog::RepositoryPropertyDialog(BasicMainWindow *parent, GitPtr g, RepositoryItem const &item, bool open_repository_menu)
: BasicRepositoryDialog(parent, g)
, ui(new Ui::RepositoryPropertyDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
repository = item;
ui->groupBox_remote->setVisible(open_repository_menu);
ui->label_name->setText(repository.name);
ui->lineEdit_local_dir->setText(misc::normalizePathSeparator(repository.local_dir));
updateRemotesTable();
}
RepositoryPropertyDialog::~RepositoryPropertyDialog()
{
delete ui;
}
void RepositoryPropertyDialog::updateRemotesTable()
{
BasicRepositoryDialog::updateRemotesTable(ui->tableWidget);
}
void RepositoryPropertyDialog::toggleRemoteMenuActivity()
{
ui->groupBox_remote->setVisible(!ui->groupBox_remote->isVisible());
}
bool RepositoryPropertyDialog::execEditRemoteDialog(Git::Remote *remote, EditRemoteDialog::Operation op)
{
auto const *list = remotes();
if (op == EditRemoteDialog::RemoteSet) {
int row = ui->tableWidget->currentRow();
if (row >= 0 && row < list->size()) {
*remote = list->at(row);
}
} else {
*remote = Git::Remote();
}
if (remote->name.isEmpty() && list->isEmpty()) {
op = EditRemoteDialog::RemoteAdd;
remote->name = "origin";
}
EditRemoteDialog dlg(mainwindow(), op);
dlg.setName(remote->name);
dlg.setUrl(remote->url);
if (dlg.exec() == QDialog::Accepted) {
remote->name = dlg.name();
remote->url = dlg.url();
GitPtr g = git();
if (op == EditRemoteDialog::RemoteAdd) {
bool ok = true;
for (Git::Remote const &r : *list) {
if (r.name == remote->name) {
qDebug() << "remote add: Already exists" << remote->name;
ok = false;
break;
}
}
if (ok) {
g->addRemoteURL(remote->name, remote->url);
}
} else if (op == EditRemoteDialog::RemoteSet) {
g->setRemoteURL(remote->name, remote->url);
}
updateRemotesTable();
return true;
}
return false;
}
Git::Remote RepositoryPropertyDialog::selectedRemote() const
{
Git::Remote remote;
auto const *list = remotes();
int row = ui->tableWidget->currentRow();
if (row >= 0 && row < list->size()) {
remote = list->at(row);
}
return remote;
}
bool RepositoryPropertyDialog::isRemoteChanged() const
{
return remote_changed;
}
void RepositoryPropertyDialog::on_pushButton_remote_add_clicked()
{
Git::Remote r;
if (execEditRemoteDialog(&r, EditRemoteDialog::RemoteAdd)) {
remote_changed = true;
}
}
void RepositoryPropertyDialog::on_pushButton_remote_edit_clicked()
{
Git::Remote remote = selectedRemote();
if (execEditRemoteDialog(&remote, EditRemoteDialog::RemoteSet)) {
remote_changed = true;
}
}
void RepositoryPropertyDialog::on_pushButton_remote_remove_clicked()
{
Git::Remote remote = selectedRemote();
if (!remote.name.isEmpty()) {
int r = QMessageBox::warning(this, tr("Confirm Remove"), tr("Are you sure you want to remove the remote '%1' from the repository '%2' ?").arg(remote.name).arg(repository.name), QMessageBox::Ok, QMessageBox::Cancel);
if (r == QMessageBox::Ok) {
GitPtr g = git();
g->removeRemote(remote.name);
updateRemotesTable();
remote_changed = true;
}
}
}
void RepositoryPropertyDialog::on_pushButton_remote_menu_clicked()
{
toggleRemoteMenuActivity();
}
diff --git a/src/SetGpgSigningDialog.cpp b/src/SetGpgSigningDialog.cpp
index 337c5d1..532808e 100644
--- a/src/SetGpgSigningDialog.cpp
+++ b/src/SetGpgSigningDialog.cpp
@@ -1,135 +1,135 @@
#include "SelectGpgKeyDialog.h"
#include "SetGpgSigningDialog.h"
#include "ui_SetGpgSigningDialog.h"
#include "ApplicationGlobal.h"
#include "MainWindow.h"
#include "ConfigSigningDialog.h"
struct SetGpgSigningDialog::Private {
QList<gpg::Data> keys;
QString global_key_id;
QString repository_key_id;
};
SetGpgSigningDialog::SetGpgSigningDialog(QWidget *parent, QString const &repo, QString const &global_key_id, QString const &repository_key_id)
: QDialog(parent)
, ui(new Ui::SetGpgSigningDialog)
, m(new Private)
{
ui->setupUi(this);
gpg::listKeys(global->gpg_command, &m->keys);
m->global_key_id = global_key_id;
m->repository_key_id = repository_key_id;
if (repo.isEmpty()) {
ui->radioButton_repository->setEnabled(false);
} else {
QString text = tr("Repository");
text += " : ";
text += repo;
ui->radioButton_repository->setText(text);
}
QRadioButton *w = repository_key_id.isEmpty() ? ui->radioButton_global : ui->radioButton_repository;
w->setFocus();
w->click();
}
SetGpgSigningDialog::~SetGpgSigningDialog()
{
delete m;
delete ui;
}
MainWindow *SetGpgSigningDialog::mainwindow()
{
return qobject_cast<MainWindow *>(parent());
}
bool SetGpgSigningDialog::isGlobalChecked() const
{
return ui->radioButton_global->isChecked();
}
bool SetGpgSigningDialog::isRepositoryChecked() const
{
return ui->radioButton_repository->isChecked();
}
-void SetGpgSigningDialog::setKey_(const gpg::Data &key)
+void SetGpgSigningDialog::setKey_(gpg::Data const &key)
{
if (isGlobalChecked()) m->global_key_id = key.id;
if (isRepositoryChecked()) m->repository_key_id = key.id;
ui->lineEdit_id->setText(key.id);
ui->lineEdit_name->setText(key.name);
ui->lineEdit_mail->setText(key.mail);
}
void SetGpgSigningDialog::setKey_(QString const &key_id)
{
gpg::Data key;
for (gpg::Data const &k : m->keys) {
if (k.id.compare(key_id, Qt::CaseInsensitive) == 0) {
key = k;
break;
}
}
setKey_(key);
}
QString SetGpgSigningDialog::id() const
{
return ui->lineEdit_id->text();
}
QString SetGpgSigningDialog::name() const
{
return ui->lineEdit_name->text();
}
QString SetGpgSigningDialog::mail() const
{
return ui->lineEdit_mail->text();
}
void SetGpgSigningDialog::on_pushButton_select_clicked()
{
gpg::listKeys(global->gpg_command, &m->keys);
SelectGpgKeyDialog dlg(this, m->keys);
if (dlg.exec() == QDialog::Accepted) {
gpg::Data key = dlg.key();
for (gpg::Data const &k : m->keys) {
if (k.id.compare(key.id, Qt::CaseInsensitive) == 0) {
key = k;
break;
}
}
setKey_(key);
}
}
void SetGpgSigningDialog::on_pushButton_clear_clicked()
{
setKey_(gpg::Data());
}
void SetGpgSigningDialog::on_radioButton_global_clicked()
{
setKey_(m->global_key_id);
}
void SetGpgSigningDialog::on_radioButton_repository_clicked()
{
setKey_(m->repository_key_id);
}
void SetGpgSigningDialog::on_pushButton_configure_clicked()
{
ConfigSigningDialog dlg(this, mainwindow(), true);
dlg.exec();
}
diff --git a/src/SetUserDialog.h b/src/SetUserDialog.h
index 4ddc934..958fa28 100644
--- a/src/SetUserDialog.h
+++ b/src/SetUserDialog.h
@@ -1,39 +1,39 @@
#ifndef SETUSERDIALOG_H
#define SETUSERDIALOG_H
#include "Git.h"
#include <QDialog>
class BasicMainWindow;
namespace Ui {
class SetUserDialog;
}
class SetUserDialog : public QDialog {
Q_OBJECT
private:
Ui::SetUserDialog *ui;
struct Private;
Private *m;
void setAvatar(QIcon icon);
BasicMainWindow *mainwindow();
public:
- explicit SetUserDialog(BasicMainWindow *parent, const Git::User &global_user, const Git::User &repo_user, QString const &repo);
+ explicit SetUserDialog(BasicMainWindow *parent, Git::User const &global_user, Git::User const &repo_user, QString const &repo);
~SetUserDialog() override;
bool isGlobalChecked() const;
bool isRepositoryChecked() const;
Git::User user() const;
private slots:
void on_radioButton_global_toggled(bool checked);
void on_radioButton_repository_toggled(bool checked);
void on_lineEdit_name_textChanged(QString const &text);
void on_lineEdit_mail_textChanged(QString const &text);
void on_pushButton_get_icon_clicked();
};
#endif // SETUSERDIALOG_H
diff --git a/src/common/misc.cpp b/src/common/misc.cpp
index d305bb9..301603f 100644
--- a/src/common/misc.cpp
+++ b/src/common/misc.cpp
@@ -1,463 +1,463 @@
#include "common/misc.h"
#include <QDebug>
#include <QFileInfo>
#include <QPainter>
#include <QProcess>
#include <QWidget>
#include <QContextMenuEvent>
#include <vector>
#include "common/joinpath.h"
#include "misc.h"
QString misc::getApplicationDir()
{
QString path = QApplication::applicationFilePath();
int i = path.lastIndexOf('\\');
int j = path.lastIndexOf('/');
if (i < j) i = j;
if (i > 0) path = path.mid(0, i);
return path;
}
QStringList misc::splitLines(QByteArray const &ba, std::function<QString(char const *ptr, size_t len)> const &tos)
{
QStringList list;
char const *begin = ba.data();
char const *end = begin + ba.size();
char const *ptr = begin;
char const *left = ptr;
while (1) {
ushort c = 0;
if (ptr < end) {
c = *ptr;
}
if (c == '\n' || c == '\r' || c == 0) {
list.push_back(tos(left, ptr - left));
if (c == 0) break;
if (c == '\n') {
ptr++;
} else if (c == '\r') {
ptr++;
if (ptr < end && *ptr == '\n') {
ptr++;
}
}
left = ptr;
} else {
ptr++;
}
}
return list;
}
QStringList misc::splitLines(QString const &text)
{
QStringList list;
ushort const *begin = text.utf16();
ushort const *end = begin + text.size();
ushort const *ptr = begin;
ushort const *left = ptr;
while (1) {
ushort c = 0;
if (ptr < end) {
c = *ptr;
}
if (c == '\n' || c == '\r' || c == 0) {
list.push_back(QString::fromUtf16(left, ptr - left));
if (c == 0) break;
if (c == '\n') {
ptr++;
} else if (c == '\r') {
ptr++;
if (ptr < end && *ptr == '\n') {
ptr++;
}
}
left = ptr;
} else {
ptr++;
}
}
return list;
}
void misc::splitLines(char const *begin, char const *end, std::vector<std::string> *out, bool keep_newline)
{
char const *ptr = begin;
char const *left = ptr;
while (1) {
char c = 0;
if (ptr < end) {
c = *ptr;
}
if (c == '\n' || c == '\r' || c == 0) {
char const *right = ptr;
if (c == '\n') {
ptr++;
} else if (c == '\r') {
ptr++;
if (ptr < end && *ptr == '\n') {
ptr++;
}
}
if (keep_newline) {
right = ptr;
}
out->push_back(std::string(left, right - left));
if (c == 0) break;
left = ptr;
} else {
ptr++;
}
}
}
void misc::splitLines(std::string const &text, std::vector<std::string> *out, bool need_crlf)
{
char const *begin = text.c_str();
char const *end = begin + text.size();
splitLines(begin, end, out, need_crlf);
}
QStringList misc::splitWords(QString const &text)
{
QStringList list;
ushort const *begin = text.utf16();
ushort const *end = begin + text.size();
ushort const *ptr = begin;
ushort const *left = ptr;
while (1) {
ushort c = 0;
if (ptr < end) {
c = *ptr;
}
if (QChar::isSpace(c) || c == 0) {
if (left < ptr) {
list.push_back(QString::fromUtf16(left, ptr - left));
}
if (c == 0) break;
ptr++;
left = ptr;
} else {
ptr++;
}
}
return list;
}
QString misc::getFileName(QString const &path)
{
int i = path.lastIndexOf('/');
int j = path.lastIndexOf('\\');
if (i < j) i = j;
if (i >= 0) {
return path.mid(i + 1);
}
return path;
}
QString misc::makeDateTimeString(QDateTime const &dt)
{
if (dt.isValid()) {
#if 0
char tmp[100];
sprintf(tmp, "%04u-%02u-%02u %02u:%02u:%02u"
, dt.date().year()
, dt.date().month()
, dt.date().day()
, dt.time().hour()
, dt.time().minute()
, dt.time().second()
);
return tmp;
#elif 0
QString s = dt.toLocalTime().toString(Qt::DefaultLocaleShortDate);
return s;
#else
QString s = dt.toLocalTime().toString(Qt::ISODate);
s.replace('T', ' ');
return s;
#endif
}
return QString();
}
-bool misc::starts_with(const std::string &str, const std::string &with)
+bool misc::starts_with(std::string const &str, std::string const &with)
{
return strncmp(str.c_str(), with.c_str(), with.size()) == 0;
}
-std::string misc::mid(const std::string &str, int start, int length)
+std::string misc::mid(std::string const &str, int start, int length)
{
int size = (int)str.size();
if (length < 0) length = size;
length += start;
if (start < 0) start = 0;
if (length < 0) length = 0;
if (start > size) start = size;
if (length > size) length = size;
length -= start;
return std::string(str.c_str() + start, length);
}
#ifdef _WIN32
QString misc::normalizePathSeparator(QString const &str)
{
if (!str.isEmpty()) {
ushort const *s = str.utf16();
size_t n = str.size();
std::vector<ushort> v;
v.reserve(n);
for (size_t i = 0; i < n; i++) {
ushort c = s[i];
if (c == '/') {
c = '\\';
}
v.push_back(c);
}
ushort const *p = &v[0];
return QString::fromUtf16(p, n);
}
return QString();
}
#else
QString misc::normalizePathSeparator(QString const &s)
{
return s;
}
#endif
QString misc::joinWithSlash(QString const &left, QString const &right)
{
if (!left.isEmpty() && !right.isEmpty()) {
return joinpath(left, right);
}
return !left.isEmpty() ? left : right;
}
void misc::setFixedSize(QWidget *w)
{
Qt::WindowFlags flags = w->windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
flags |= Qt::MSWindowsFixedSizeDialogHint;
w->setWindowFlags(flags);
w->setFixedSize(w->size());
}
void misc::drawFrame(QPainter *pr, int x, int y, int w, int h, QColor color_topleft, QColor color_bottomright)
{
if (w < 3 || h < 3) {
if (w > 0 && h > 0) {
pr->fillRect(x, y, w, h, color_topleft);
}
} else {
if (!color_topleft.isValid()) color_topleft = Qt::black;
if (!color_bottomright.isValid()) color_bottomright = color_topleft;
pr->fillRect(x, y, w - 1, 1, color_topleft);
pr->fillRect(x, y + 1, 1, h -1, color_topleft);
pr->fillRect(x + w - 1, y, 1, h -1, color_bottomright);
pr->fillRect(x + 1, y + h - 1, w - 1, 1, color_bottomright);
}
}
void misc::dump(uint8_t const *ptr, size_t len)
{
if (ptr && len > 0) {
size_t pos = 0;
while (pos < len) {
char tmp[100];
char *dst = tmp;
sprintf(dst, "%08llX ", ((unsigned long long)(pos)));
dst += 9;
for (int i = 0; i < 16; i++) {
if (pos + i < len) {
sprintf(dst, "%02X ", ptr[pos + i] & 0xff);
} else {
sprintf(dst, " ");
}
dst += 3;
}
for (int i = 0; i < 16; i++) {
int c = ' ';
if (pos < len) {
c = ptr[pos] & 0xff;
if (!isprint(c)) {
c = '.';
}
pos++;
}
*dst = c;
dst++;
}
*dst = 0;
qDebug() << tmp;
}
}
}
-void misc::dump(const QByteArray *in)
+void misc::dump(QByteArray const *in)
{
size_t len = 0;
uint8_t const *ptr = nullptr;
if (in) {
len = in->size();
if (len > 0) {
ptr = (uint8_t const *)in->data();
}
}
dump(ptr, len);
}
bool misc::isText(QString const &mimetype)
{
return mimetype.startsWith("text/");
}
bool misc::isSVG(QString const &mimetype)
{
if (mimetype == "image/svg") return true;
if (mimetype == "image/svg+xml") return true;
return false;
}
bool misc::isPSD(QString const &mimetype)
{
if (mimetype == "image/vnd.adobe.photoshop") return true;
return false;
}
bool misc::isImage(QString const &mimetype)
{
#if 0
if (mimetype == "image/jpeg") return true;
if (mimetype == "image/jpg") return true;
if (mimetype == "image/png") return true;
if (mimetype == "image/gif") return true;
if (mimetype == "image/bmp") return true;
if (mimetype == "image/x-ms-bmp") return true;
if (mimetype == "image/x-icon") return true;
if (mimetype == "image/tiff") return true;
if (isSVG(mimetype)) return true;
if (isPSD(mimetype)) return true;
return false;
#else
return mimetype.startsWith("image/");
#endif
}
QString misc::abbrevBranchName(QString const &name)
{
QStringList sl = name.split('/');
if (sl.size() == 1) return sl[0];
QString newname;
for (int i = 0; i < sl.size(); i++) {
QString s = sl[i];
if (i + 1 < sl.size()) {
s = s.mid(0, 1);
}
if (i > 0) {
newname += '/';
}
newname += s;
}
return newname;
}
QString misc::determinFileType(QString const &filecommand, QString const &path, bool mime, std::function<void (QString const &, QByteArray *)> const &callback)
{ // ファイルタイプを調べる
if (QFileInfo(filecommand).isExecutable()) {
QString const &file = filecommand;
QString mgc;
#ifdef Q_OS_WIN
int i = file.lastIndexOf('/');
int j = file.lastIndexOf('\\');
if (i < j) i = j;
if (i >= 0) {
mgc = file.mid(0, i + 1) + "magic.mgc";
if (QFileInfo(mgc).isReadable()) {
// ok
} else {
mgc = QString();
}
}
#endif
QString cmd;
if (mgc.isEmpty()) {
cmd = "\"%1\"";
cmd = cmd.arg(file);
} else {
cmd = "\"%1\" -m \"%2\"";
cmd = cmd.arg(file).arg(mgc);
cmd = cmd.replace('\\', '/');
}
if (mime) {
cmd += " --mime";
}
cmd += " --brief ";
if (path == "-") {
cmd += path;
} else {
cmd += QString("\"%1\"").arg(path);
}
cmd = misc::normalizePathSeparator(cmd);
// run file command
QByteArray ba;
callback(cmd, &ba);
// parse file type
if (!ba.isEmpty()) {
QString s = QString::fromUtf8(ba).trimmed();
QStringList list = s.split(';', QString::SkipEmptyParts);
if (!list.isEmpty()) {
QString mimetype = list[0].trimmed();
return mimetype;
}
}
} else {
qDebug() << "No executable 'file' command";
}
return QString();
}
std::string misc::makeProxyServerURL(std::string text)
{
if (!text.empty() && !strstr(text.c_str(), "://")) {
text = "http://" + text;
if (text[text.size() - 1] != '/') {
text += '/';
}
}
return text;
}
QString misc::makeProxyServerURL(QString text)
{
if (!text.isEmpty() && text.indexOf("://") < 0) {
text = "http://" + text;
if (text[text.size() - 1] != '/') {
text += '/';
}
}
return text;
}
QPoint misc::contextMenuPos(QWidget *w, QContextMenuEvent *e)
{
if (e && e->reason() == QContextMenuEvent::Mouse) {
return QCursor::pos() + QPoint(8, -8);
}
return w->mapToGlobal(QPoint(4, 4));
}
bool misc::isExecutable(QString const &cmd)
{
QFileInfo info(cmd);
return info.isExecutable();
}
diff --git a/src/common/misc.h b/src/common/misc.h
index 8797d0d..10f6fe3 100644
--- a/src/common/misc.h
+++ b/src/common/misc.h
@@ -1,57 +1,57 @@
#ifndef MISC_H
#define MISC_H
#include <QApplication>
#include <QStringList>
#include <QDateTime>
#include <functional>
#include <vector>
#include <cstdint>
#include <QColor>
class QContextMenuEvent;
class misc {
public:
static QString getApplicationDir();
static QStringList splitLines(QByteArray const &text, std::function<QString(char const *ptr, size_t len)> const &tos);
static QStringList splitLines(QString const &text);
static void splitLines(char const *begin, char const *end, std::vector<std::string> *out, bool keep_newline);
- static void splitLines(const std::string &text, std::vector<std::string> *out, bool need_crlf);
+ static void splitLines(std::string const &text, std::vector<std::string> *out, bool need_crlf);
static QStringList splitWords(QString const &text);
static QString getFileName(QString const &path);
static QString makeDateTimeString(const QDateTime &dt);
static bool starts_with(std::string const &str, std::string const &with);
static std::string mid(std::string const &str, int start, int length = -1);
static QString normalizePathSeparator(QString const &str);
static QString joinWithSlash(QString const &left, QString const &right);
static void setFixedSize(QWidget *w);
static void drawFrame(QPainter *pr, int x, int y, int w, int h, QColor color_topleft, QColor color_bottomright = QColor());
static void dump(const uint8_t *ptr, size_t len);
static void dump(QByteArray const *in);
static bool isText(QString const &mimetype);
static bool isImage(QString const &mimetype);
static bool isSVG(QString const &mimetype);
static bool isPSD(QString const &mimetype);
static QString abbrevBranchName(QString const &name);
static QString determinFileType(QString const &filecommand, QString const &path, bool mime, std::function<void(QString const &, QByteArray *)> const &callback);
static std::string makeProxyServerURL(std::string text);
static QString makeProxyServerURL(QString text);
static QPoint contextMenuPos(QWidget *w, QContextMenuEvent *e);
static bool isExecutable(QString const &cmd);
};
class OverrideWaitCursor_ {
public:
OverrideWaitCursor_()
{
qApp->setOverrideCursor(Qt::WaitCursor);
}
~OverrideWaitCursor_()
{
qApp->restoreOverrideCursor();
}
};
#define OverrideWaitCursor OverrideWaitCursor_ waitcursor_; (void)waitcursor_;
#endif // MISC_H
diff --git a/src/darktheme/DarkStyle.cpp b/src/darktheme/DarkStyle.cpp
index ce22dee..f1ef74c 100644
--- a/src/darktheme/DarkStyle.cpp
+++ b/src/darktheme/DarkStyle.cpp
@@ -1,2258 +1,2258 @@
#include "DarkStyle.h"
#include "NinePatch.h"
#include "TraditionalWindowsStyleTreeControl.h"
#include <QApplication>
#include <QComboBox>
#include <QDebug>
#include <QDialogButtonBox>
#include <QDockWidget>
#include <QInputDialog>
#include <QListView>
#include <QMessageBox>
#include <QPixmapCache>
#include <QStyleOptionComplex>
#include <QStyleOptionFrameV3>
#include <QTableWidget>
#include <QToolTip>
#include <cmath>
#include <cstdint>
#define MBI_NORMAL 1
#define MBI_HOT 2
#define MBI_PUSHED 3
#define MBI_DISABLED 4
namespace {
const int windowsItemFrame = 2; // menu item frame width
const int windowsItemHMargin = 3; // menu item hor text margin
const int windowsItemVMargin = 4; // menu item ver text margin
const int windowsArrowHMargin = 6; // arrow horizontal margin
const int windowsRightBorder = 15; // right border on windows
void drawFrame(QPainter *pr, int x, int y, int w, int h, QColor color_topleft, QColor color_bottomright)
{
if (w < 3 || h < 3) {
if (w > 0 && h > 0) {
pr->fillRect(x, y, w, h, color_topleft);
}
} else {
if (!color_topleft.isValid()) color_topleft = Qt::black;
if (!color_bottomright.isValid()) color_bottomright = color_topleft;
pr->fillRect(x, y, w - 1, 1, color_topleft);
pr->fillRect(x, y + 1, 1, h -1, color_topleft);
pr->fillRect(x + w - 1, y, 1, h -1, color_bottomright);
pr->fillRect(x + 1, y + h - 1, w - 1, 1, color_bottomright);
}
}
void drawFrame(QPainter *pr, QRect const &r, QColor const &color_topleft, QColor const &color_bottomright)
{
return drawFrame(pr, r.x(), r.y(), r.width(), r.height(), color_topleft, color_bottomright);
}
struct TextureCacheItem {
QString key;
QPixmap pm;
};
QString pixmapkey(QString const &name, QString const &role, QSize const &size, QColor const &color)
{
QString key = "%1:%2:%3:%4:%5";
key = key.arg(name).arg(role).arg(size.width()).arg(size.height()).arg(QString().sprintf("%02X%02X%02X", color.red(), color.green(), color.blue()));
return key;
}
-void drawRaisedFrame(QPainter *p, const QRect &rect, const QPalette &palette)
+void drawRaisedFrame(QPainter *p, QRect const &rect, const QPalette &palette)
{
p->save();
int x = rect.x();
int y = rect.y();
int w = rect.width();
int h = rect.height();
p->setClipRect(x, y, w, h);
p->fillRect(x, y, w, h, palette.color(QPalette::Window));
p->fillRect(x, y, w - 1, 1, palette.color(QPalette::Light));
p->fillRect(x, y, 1, h - 1, palette.color(QPalette::Light));
p->fillRect(x + 1, y + h - 2, w - 2, 1, palette.color(QPalette::Dark));
p->fillRect(x + w - 2, y + 1, 1, h - 2, palette.color(QPalette::Dark));
p->fillRect(x, y + h - 1, w, 1, palette.color(QPalette::Shadow));
p->fillRect(x + w - 1, y, 1, h, palette.color(QPalette::Shadow));
p->restore();
}
QImage loadImage(QString const &path, QString const &role = QString())
{
QImage image(path);
image.setText("name", path);
image.setText("role", role);
return image;
}
QRgb colorize(QRgb color, int light, int alpha)
{
int r, g, b;
r = g = b = 0;
if (alpha != 0) {
r = qRed(color);
g = qGreen(color);
b = qBlue(color);
if (light < 128) {
r = r * light / 127;
g = g * light / 127;
b = b * light / 127;
} else {
int u = 255 - light;
r = 255 - (255 - r) * u / 127;
g = 255 - (255 - g) * u / 127;
b = 255 - (255 - b) * u / 127;
}
}
return qRgba(r, g, b, alpha);
}
} // namespace
// DarkStyle
static const int TEXTURE_CACHE_SIZE = 100;
struct DarkStyle::Private {
QColor base_color;
bool images_loaded = false;
ScrollBarTextures hsb;
ScrollBarTextures vsb;
int scroll_bar_extent = -1;
QImage button_normal;
QImage button_press;
QImage progress_horz;
QImage progress_vert;
TraditionalWindowsStyleTreeControl legacy_windows;
};
DarkStyle::DarkStyle(QColor const &base_color)
: m(new Private)
{
setBaseColor(base_color);
}
DarkStyle::~DarkStyle()
{
delete m;
}
QColor DarkStyle::getBaseColor()
{
return m->base_color;
}
void DarkStyle::setBaseColor(QColor const &color)
{
m->base_color = color;
m->images_loaded = false;
}
QColor DarkStyle::color(int level, int alpha) const
{
QColor c = m->base_color.lighter(level * 100 / 255);
c.setAlpha(alpha);
return c;
}
void DarkStyle::setScrollBarExtent(int n)
{
m->scroll_bar_extent = n;
}
QImage DarkStyle::colorizeImage(QImage image)
{
int w = image.width();
int h = image.height();
if (w > 0 && h > 0) {
QColor c = color(128);
QRgb rgb = c.rgb();
Q_ASSERT(image.format() == QImage::Format_ARGB32);
for (int y = 0; y < h; y++) {
QRgb *p = reinterpret_cast<QRgb *>(image.scanLine(y));
for (int x = 0; x < w; x++) {
p[x] = colorize(rgb, qGray(p[x]), qAlpha(p[x]));
}
}
}
return image;
}
QImage DarkStyle::loadColorizedImage(QString const &path, QString const &role)
{
QImage image = loadImage(path);
image = colorizeImage(image);
image.setText("name", path);
image.setText("role", role);
return image;
}
namespace {
class Lighten {
private:
int lut[256];
public:
Lighten()
{
#if 0
const double x = 0.75;
for (int i = 0; i < 256; i++) {
lut[i] = (int)(pow(i / 255.0, x) * 255);
}
#else
for (int i = 0; i < 256; i++) {
lut[i] = 255 - (255 - i) * 192 / 256;
}
#endif
}
int operator [] (int i)
{
return lut[i];
}
};
}
DarkStyle::ButtonImages DarkStyle::generateButtonImages(QString const &path)
{
QImage source = loadImage(path);
ButtonImages buttons;
int w = source.width();
int h = source.height();
if (w > 4 && h > 4) {
QColor c = color(128);
QRgb rgb = c.rgb();
Lighten lighten;
buttons.im_normal = source;
buttons.im_hover = source;
w -= 4;
h -= 4;
for (int y = 0; y < h; y++) {
QRgb *src1 = reinterpret_cast<QRgb *>(source.scanLine(y + 1));
QRgb *src2 = reinterpret_cast<QRgb *>(source.scanLine(y + 2));
QRgb *src3 = reinterpret_cast<QRgb *>(source.scanLine(y + 3));
QRgb *dst0 = reinterpret_cast<QRgb *>(buttons.im_normal.scanLine(y + 2)) + 2;
QRgb *dst1 = reinterpret_cast<QRgb *>(buttons.im_hover.scanLine(y + 2)) + 2;
for (int x = 0; x < w; x++) {
int v = (int)qAlpha(src3[x + 3]) - (int)qAlpha(src1[x + 1]);
v = (v + 256) / 2;
int alpha = qAlpha(src2[x + 2]);
dst0[x] = colorize(rgb, v, alpha);
v = lighten[v];
dst1[x] = colorize(rgb, v, alpha);
}
}
buttons.im_normal.setText("name", source.text("name"));
buttons.im_hover.setText("name", source.text("name"));
buttons.im_normal.setText("role", "normal");
buttons.im_hover.setText("role", "hover");
}
return buttons;
}
QImage DarkStyle::generateHoverImage(QImage const &source)
{
QImage newimage;
int w = source.width();
int h = source.height();
if (w > 2 && h > 2) {
QColor c = color(128);
QRgb rgb = c.rgb();
Lighten lighten;
newimage = source;
Q_ASSERT(newimage.format() == QImage::Format_ARGB32);
w -= 2;
h -= 2;
for (int y = 0; y < h; y++) {
QRgb *ptr = reinterpret_cast<QRgb *>(newimage.scanLine(y + 1)) + 1;
for (int x = 0; x < w; x++) {
int v = qGray(ptr[x]);
v = lighten[v];
ptr[x] = colorize(rgb, v, qAlpha(ptr[x]));
}
}
newimage.setText("role", "hover");
}
return newimage;
}
void DarkStyle::loadImages()
{
if (m->images_loaded) return;
if (!m->base_color.isValid()) {
setBaseColor(Qt::white);
}
m->button_normal = loadColorizedImage(QLatin1String(":/darktheme/button/button_normal.png"), QLatin1String("normal"));
m->button_press = loadColorizedImage(QLatin1String(":/darktheme/button/button_press.png"), QLatin1String("press"));
m->hsb.sub_line = generateButtonImages(QLatin1String(":/darktheme/hsb/hsb_sub_line.png"));
m->hsb.add_line = generateButtonImages(QLatin1String(":/darktheme/hsb/hsb_add_line.png"));
m->hsb.page_bg = loadColorizedImage(QLatin1String(":/darktheme/hsb/hsb_page_bg.png"));
m->hsb.slider.im_normal = loadColorizedImage(QLatin1String(":/darktheme/hsb/hsb_slider.png"));
m->hsb.slider.im_hover = generateHoverImage(m->hsb.slider.im_normal);
m->vsb.sub_line = generateButtonImages(QLatin1String(":/darktheme/vsb/vsb_sub_line.png"));
m->vsb.add_line = generateButtonImages(QLatin1String(":/darktheme/vsb/vsb_add_line.png"));
m->vsb.page_bg = loadColorizedImage(QLatin1String(":/darktheme/vsb/vsb_page_bg.png"));
m->vsb.slider.im_normal = loadColorizedImage(QLatin1String(":/darktheme/vsb/vsb_slider.png"));
m->vsb.slider.im_hover = generateHoverImage(m->vsb.slider.im_normal);
m->progress_horz = loadImage(QLatin1String(":/darktheme/progress/horz.png"));
m->progress_vert = loadImage(QLatin1String(":/darktheme/progress/vert.png"));
m->images_loaded = true;
}
QPixmap DarkStyle::pixmapFromImage(const QImage &image, QSize size) const
{
QString key = pixmapkey(image.text("name"), image.text("role"), size, m->base_color);
QPixmap *pm = QPixmapCache::find(key);
if (pm) return *pm;
TextureCacheItem t;
t.key = key;
t.pm = QPixmap::fromImage(image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
QPixmapCache::insert(key, t.pm);
return t.pm;
}
-QColor DarkStyle::colorForSelectedFrame(const QStyleOption *opt) const
+QColor DarkStyle::colorForSelectedFrame(QStyleOption const *opt) const
{
(void)opt;
return QColor(128, 192, 255);
}
QColor DarkStyle::colorForItemView(QStyleOption const *opt) const
{
return opt->palette.color(QPalette::Dark);
// return opt->palette.color(QPalette::Base); // これじゃない
}
-void DarkStyle::drawNinePatchImage(QPainter *p, const QImage &image, const QRect &r, int w, int h) const
+void DarkStyle::drawNinePatchImage(QPainter *p, const QImage &image, QRect const &r, int w, int h) const
{
QImage im = createImageFromNinePatchImage(image, w, h);
im.setText("name", image.text("name"));
im.setText("role", image.text("role"));
QPixmap pm = pixmapFromImage(im, r.size());
p->drawPixmap(r.x(), r.y(), pm);
}
void DarkStyle::polish(QPalette &palette)
{
if (!m->base_color.isValid()) {
setBaseColor(Qt::white);
}
loadImages();
palette = QPalette(color(64));
#ifndef Q_OS_WIN
palette.setColor(QPalette::ToolTipText, Qt::black); // ツールチップの文字色
#endif
}
-void DarkStyle::drawGutter(QPainter *p, const QRect &r) const
+void DarkStyle::drawGutter(QPainter *p, QRect const &r) const
{
int x = r.x();
int y = r.y();
int w = r.width();
int h = r.height();
QColor dark = color(32);
QColor lite = color(128);
if (w < h) {
x += (w - 1) / 2;
p->fillRect(x, y, 1, h, dark);
p->fillRect(x + 1, y, 1, h, lite);
} else if (w > h) {
y += (h - 1) / 2;
p->fillRect(x, y, w, 1, dark);
p->fillRect(x, y + 1, w, 1, lite);
}
}
-void DarkStyle::drawSelectedMenuFrame(const QStyleOption *option, QPainter *p, QRect rect, const QWidget *widget, bool deep) const
+void DarkStyle::drawSelectedMenuFrame(QStyleOption const *option, QPainter *p, QRect rect, QWidget const *widget, bool deep) const
{
(void)widget;
QColor color = colorForSelectedFrame(option);
int x, y, w, h;
x = rect.x();
y = rect.y();
w = rect.width();
h = rect.height();
auto SetAlpha = [&](QColor *color, int alpha){
if (deep) {
alpha = alpha * 3 / 2;
}
color->setAlpha(alpha);
};
QString key = QString().sprintf("selection_frame:%02x%02x%02x:%dx%d", color.red(), color.green(), color.blue(), w, h);
QPixmap pixmap;
if (!QPixmapCache::find(key, &pixmap)) {
pixmap = QPixmap(w, h);
pixmap.fill(Qt::transparent);
QPainter pr(&pixmap);
pr.setRenderHint(QPainter::Antialiasing);
QColor pencolor = color;
SetAlpha(&pencolor, 128);
pr.setPen(pencolor);
pr.setBrush(Qt::NoBrush);
QPainterPath path;
path.addRoundedRect(1.5, 1.5, w - 1.5, h - 1.5, 3, 3);
pr.drawPath(path);
pr.setClipPath(path);
int a = color.alpha();
QColor color0 = color;
QColor color1 = color;
SetAlpha(&color0, 96 * a / 255);
SetAlpha(&color1, 32 * a / 255);
QLinearGradient gr(QPointF(0, 0), QPointF(0, h));
gr.setColorAt(0, color0);
gr.setColorAt(1, color1);
QBrush br(gr);
pr.fillRect(0, 0, w, h, br);
pr.end();
QPixmapCache::insert(key, pixmap);
}
p->drawPixmap(x, y, w, h, pixmap);
}
-void DarkStyle::drawButton(QPainter *p, const QStyleOption *option) const
+void DarkStyle::drawButton(QPainter *p, QStyleOption const *option) const
{
QRect rect = option->rect;
int w = rect.width();
int h = rect.height();
#ifdef Q_OS_MAC
int margin = pixelMetric(PM_ButtonMargin, option, nullptr);
if (margin > 0) {
int n = std::min(w, h);
if (n > margin * 2) {
n = (n - margin * 2) / 2;
if (n > margin) n = margin;
rect = rect.adjusted(n, n, -n, -n);
w = rect.width();
h = rect.height();
}
}
#endif
bool pressed = (option->state & (State_Sunken | State_On));
bool hover = (option->state & State_MouseOver);
if (pressed) {
drawNinePatchImage(p, m->button_press, rect, w, h);
} else {
drawNinePatchImage(p, m->button_normal, rect, w, h);
}
{
QPainterPath path;
path.addRoundedRect(rect, 6, 6); // 角丸四角形のパス
p->save();
p->setRenderHint(QPainter::Antialiasing);
p->setClipPath(path);
int x = rect.x();
int y = rect.y();
int w = rect.width();
int h = rect.height();
QColor color0, color1;
#ifdef Q_OS_MAC
if (pressed) {
color0 = Qt::black;
color1 = Qt::black;
color0.setAlpha(48);
color1.setAlpha(144);
} else {
color0 = Qt::black;
color1 = Qt::black;
color0.setAlpha(32);
color1.setAlpha(128);
}
#else
if (pressed) {
color0 = Qt::black;
color1 = Qt::black;
color0.setAlpha(32);
color1.setAlpha(128);
} else if (hover) {
color0 = Qt::black;
color1 = Qt::black;
color0.setAlpha(16);
color1.setAlpha(96);
} else {
color0 = Qt::black;
color1 = Qt::black;
color0.setAlpha(32);
color1.setAlpha(128);
}
#endif
QLinearGradient gr(QPointF(x, y), QPointF(x, y + h));
gr.setColorAt(0, color0);
gr.setColorAt(1, color1);
QBrush br(gr);
p->fillRect(x, y, w, h, br);
if (option->state & State_HasFocus) {
#if 1
p->setPen(QColor(80, 160, 255));
p->setBrush(Qt::NoBrush);
double m = 3.5;
p->drawRoundedRect(((QRectF)rect).adjusted(m, m, -m, -m), 4, 4);
#else
p->fillRect(x, y, w, h, QColor(80, 160, 255, 32));
#endif
}
p->restore();
}
}
-void DarkStyle::drawToolButton(QPainter *p, const QStyleOption *option) const
+void DarkStyle::drawToolButton(QPainter *p, QStyleOption const *option) const
{
p->save();
bool hover = (option->state & State_MouseOver);
bool pressed = (option->state & State_Sunken);
#ifdef Q_OS_MAC
hover = false;
#endif
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
QColor color0, color1;
#ifdef Q_OS_MAC
if (pressed) {
color0 = color(72);
color1 = color(40);
} else {
color0 = color(80);
color1 = color(48);
}
#else
if (pressed) {
color0 = color(80);
color1 = color(48);
} else if (hover) {
color0 = color(96);
color1 = color(64);
} else {
color0 = color(80);
color1 = color(48);
}
#endif
QLinearGradient gr(QPointF(x, y), QPointF(x, y + h));
gr.setColorAt(0, color0);
gr.setColorAt(1, color1);
QBrush br(gr);
p->fillRect(x, y, w, h, br);
if (option->state & State_Raised) {
drawFrame(p, option->rect, color(96), color(32));
} else if (option->state & State_Sunken) {
drawFrame(p, option->rect, color(48), color(48));
}
p->restore();
}
-void DarkStyle::drawMenuBarBG(QPainter *p, const QStyleOption *option, QWidget const *widget) const
+void DarkStyle::drawMenuBarBG(QPainter *p, QStyleOption const *option, QWidget const *widget) const
{
int x = option->rect.x();
int y = widget->y();
int w = option->rect.width();
int h = widget->height();
QLinearGradient gradient;
gradient.setStart(x, y);
gradient.setFinalStop(x, y + h / 2.0);
gradient.setColorAt(0, option->palette.color(QPalette::Light));
gradient.setColorAt(1, option->palette.color(QPalette::Window));
p->fillRect(x, y, w, h, gradient);
p->fillRect(x, y + h - 1, w, 1, option->palette.color(QPalette::Dark));
}
-int DarkStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
+int DarkStyle::pixelMetric(PixelMetric metric, QStyleOption const *option, QWidget const *widget) const
{
if (metric == PM_ScrollBarExtent) {
if (m->scroll_bar_extent > 0) {
return m->scroll_bar_extent;
}
}
if (metric == PM_SliderLength) {
return std::min(widget->width(), widget->height());
}
return QProxyStyle::pixelMetric(metric, option, widget);
}
-QRect DarkStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *option, SubControl sc, const QWidget *widget) const
+QRect DarkStyle::subControlRect(ComplexControl cc, QStyleOptionComplex const *option, SubControl sc, QWidget const *widget) const
{
if (cc == CC_Slider && sc == SC_SliderGroove) {
return widget->rect();
}
if (cc == CC_Slider && sc == SC_SliderHandle) {
if (const auto *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
QRect rect;
int extent = std::min(widget->width(), widget->height());
int span = pixelMetric(PM_SliderLength, slider, widget);
bool horizontal = slider->orientation == Qt::Horizontal;
int sliderPos = sliderPositionFromValue(slider->minimum, slider->maximum,
slider->sliderPosition,
(horizontal ? slider->rect.width()
: slider->rect.height()) - span,
slider->upsideDown);
if (horizontal) {
rect.setRect(slider->rect.x() + sliderPos, slider->rect.y(), span, extent);
} else {
rect.setRect(slider->rect.x(), slider->rect.y() + sliderPos, extent, span);
}
return rect;
}
}
if (cc == CC_GroupBox) {
QRect ret;
if (const auto *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(option)) {
switch (sc) {
case SC_GroupBoxFrame:
case SC_GroupBoxContents:
{
int topMargin = 0;
int topHeight = 0;
int verticalAlignment = proxy()->styleHint(SH_GroupBox_TextLabelVerticalAlignment, groupBox, widget);
#ifdef Q_OS_MACX
verticalAlignment |= Qt::AlignVCenter;
#endif
if (groupBox->text.size() || (groupBox->subControls & QStyle::SC_GroupBoxCheckBox)) {
topHeight = groupBox->fontMetrics.height();
if (verticalAlignment & Qt::AlignVCenter) {
topMargin = topHeight / 2;
} else if (verticalAlignment & Qt::AlignTop) {
topMargin = topHeight;
}
}
QRect frameRect = groupBox->rect;
frameRect.setTop(topMargin);
if (sc == SC_GroupBoxFrame) {
ret = frameRect;
break;
}
int frameWidth = 0;
if ((groupBox->features & QStyleOptionFrame::Flat) == 0) {
frameWidth = proxy()->pixelMetric(PM_DefaultFrameWidth, groupBox, widget);
}
ret = frameRect.adjusted(frameWidth, frameWidth + topHeight - topMargin, -frameWidth, -frameWidth);
}
break;
case SC_GroupBoxCheckBox:
case SC_GroupBoxLabel:
{
QFontMetrics fontMetrics = groupBox->fontMetrics;
int h = fontMetrics.height();
int tw = fontMetrics.size(Qt::TextShowMnemonic, groupBox->text + QLatin1Char(' ')).width();
int marg = (groupBox->features & QStyleOptionFrame::Flat) ? 0 : 8;
ret = groupBox->rect.adjusted(marg, 0, -marg, 0);
ret.setHeight(h);
int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, option, widget);
int indicatorSpace = proxy()->pixelMetric(PM_CheckBoxLabelSpacing, option, widget) - 1;
bool hasCheckBox = groupBox->subControls & QStyle::SC_GroupBoxCheckBox;
int checkBoxSize = hasCheckBox ? (indicatorWidth + indicatorSpace) : 0;
// Adjusted rect for label + indicatorWidth + indicatorSpace
QRect totalRect = alignedRect(groupBox->direction, groupBox->textAlignment, QSize(tw + checkBoxSize, h), ret);
// Adjust totalRect if checkbox is set
if (hasCheckBox) {
bool ltr = groupBox->direction == Qt::LeftToRight;
int left = 0;
// Adjust for check box
if (sc == SC_GroupBoxCheckBox) {
int indicatorHeight = proxy()->pixelMetric(PM_IndicatorHeight, option, widget);
left = ltr ? totalRect.left() : (totalRect.right() - indicatorWidth);
int top = totalRect.top() + qMax(0, fontMetrics.height() - indicatorHeight) / 2;
totalRect.setRect(left, top, indicatorWidth, indicatorHeight);
// Adjust for label
} else {
left = ltr ? (totalRect.left() + checkBoxSize - 2) : totalRect.left();
totalRect.setRect(left, totalRect.top(), totalRect.width() - checkBoxSize, totalRect.height());
}
}
ret = totalRect;
}
break;
}
return ret;
}
}
return QProxyStyle::subControlRect(cc, option, sc, widget);
}
-void DarkStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *option, QPainter *p, const QWidget *widget) const
+void DarkStyle::drawPrimitive(PrimitiveElement pe, QStyleOption const *option, QPainter *p, QWidget const *widget) const
{
// qDebug() << pe;
#ifdef Q_OS_LINUX
if (pe == PE_FrameFocusRect) {
QColor color(64, 128, 255);
drawFrame(p, option->rect, color, color);
return;
}
#endif
if (pe == PE_IndicatorArrowDown) {
switch (pe) {
case PE_IndicatorArrowUp:
case PE_IndicatorArrowDown:
case PE_IndicatorArrowRight:
case PE_IndicatorArrowLeft:
{
if (option->rect.width() <= 1 || option->rect.height() <= 1) {
break;
}
QRect r = option->rect.adjusted(-10, -10, 0, 10);
int size = qMin(r.height(), r.width());
QPixmap pixmap;
if (1) {
int border = size / 5;
int sqsize = 2 * (size / 2);
QImage image(sqsize, sqsize, QImage::Format_ARGB32_Premultiplied);
image.fill(0);
QPainter imagePainter(&image);
QPolygon a;
switch (pe) {
case PE_IndicatorArrowUp:
a.setPoints(3, border, sqsize / 2, sqsize / 2, border, sqsize - border, sqsize / 2);
break;
case PE_IndicatorArrowDown:
a.setPoints(3, border, sqsize / 2, sqsize / 2, sqsize - border, sqsize - border, sqsize / 2);
break;
case PE_IndicatorArrowRight:
a.setPoints(3, sqsize - border, sqsize / 2, sqsize / 2, border, sqsize / 2, sqsize - border);
break;
case PE_IndicatorArrowLeft:
a.setPoints(3, border, sqsize / 2, sqsize / 2, border, sqsize / 2, sqsize - border);
break;
default:
break;
}
int bsx = 0;
int bsy = 0;
if (option->state & State_Sunken) {
bsx = pixelMetric(PM_ButtonShiftHorizontal, option, widget);
bsy = pixelMetric(PM_ButtonShiftVertical, option, widget);
}
QRect bounds = a.boundingRect();
int sx = sqsize / 2 - bounds.center().x() - 1;
int sy = sqsize / 2 - bounds.center().y() - 1;
imagePainter.translate(sx + bsx, sy + bsy);
imagePainter.setPen(option->palette.buttonText().color());
imagePainter.setBrush(option->palette.buttonText());
imagePainter.setRenderHint(QPainter::Qt4CompatiblePainting);
if (!(option->state & State_Enabled)) {
imagePainter.translate(1, 1);
imagePainter.setBrush(option->palette.light().color());
imagePainter.setPen(option->palette.light().color());
imagePainter.drawPolygon(a);
imagePainter.translate(-1, -1);
imagePainter.setBrush(option->palette.mid().color());
imagePainter.setPen(option->palette.mid().color());
}
imagePainter.drawPolygon(a);
imagePainter.end();
pixmap = QPixmap::fromImage(image);
}
int xOffset = r.x() + (r.width() - size)/2;
int yOffset = r.y() + (r.height() - size)/2;
p->drawPixmap(xOffset, yOffset, pixmap);
}
}
return;
}
if (pe == PE_PanelMenu) {
QRect r = option->rect;
drawFrame(p, r, Qt::black, Qt::black);
r = r.adjusted(1, 1, -1, -1);
drawFrame(p, r, color(128), color(64));
r = r.adjusted(1, 1, -1, -1);
p->fillRect(r, color(80));
return;
}
if (pe == PE_FrameMenu) {
return;
}
if (pe == PE_PanelButtonBevel) {
drawButton(p, option);
return;
}
if (pe == PE_PanelStatusBar) {
p->fillRect(option->rect, option->palette.color(QPalette::Window));
return;
}
if (pe == PE_FrameTabWidget) {
drawRaisedFrame(p, option->rect, option->palette);
return;
}
if (pe == PE_PanelLineEdit) {
if (const auto *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
p->fillRect(option->rect, colorForItemView(option));
if (panel->lineWidth > 0) {
drawFrame(p, option->rect, option->palette.color(QPalette::Shadow), option->palette.color(QPalette::Light));
}
}
return;
}
if (pe == PE_FrameGroupBox) {
p->save();
p->setRenderHint(QPainter::Antialiasing);
QRectF r = option->rect;
r = r.adjusted(1.5, 1.5, -0.5, -0.5);
p->setPen(option->palette.color(QPalette::Light));
p->drawRoundedRect(r, 5, 5);
r = r.adjusted(-1, -1, -1, -1);
p->setPen(option->palette.color(QPalette::Dark));
p->drawRoundedRect(r, 5, 5);
p->restore();
return;
}
if (pe == PE_PanelItemViewRow) {
#ifdef Q_OS_WIN
// thru
#else
return;
#endif
}
if (pe == PE_PanelItemViewItem) {
// p->fillRect(option->rect, colorForItemView(option)); // 選択枠を透過描画させるので背景は描かない
if (auto const *tableview = qobject_cast<QTableView const *>(widget)) {
Qt::PenStyle grid_pen_style = Qt::NoPen;
if (tableview->showGrid()) {
grid_pen_style = tableview->gridStyle();
}
QAbstractItemView::SelectionBehavior selection_behavior = tableview->selectionBehavior();
if (grid_pen_style != Qt::NoPen) {
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
p->fillRect(x + w - 1, y, 1, h, option->palette.color(QPalette::Dark));
p->fillRect(x, y + h - 1, w, 1, option->palette.color(QPalette::Dark));
}
if (option->state & State_Selected) {
p->save();
p->setClipRect(option->rect);
QRect r = widget->rect();
if (selection_behavior == QAbstractItemView::SelectionBehavior::SelectRows) {
r = QRect(r.x(), option->rect.y(), r.width(), option->rect.height());
} else if (selection_behavior == QAbstractItemView::SelectionBehavior::SelectRows) {
r = QRect(option->rect.x(), r.y(), option->rect.y(), r.height());
}
drawSelectedMenuFrame(option, p, r, widget);
p->restore();
}
} else {
int n = 0;
if (option->state & State_Selected) {
n++;
}
if (n > 0) {
drawSelectedMenuFrame(option, p, option->rect, widget, n > 1);
}
}
return;
}
if (pe == QStyle::PE_IndicatorBranch) {
QColor bg = option->palette.color(QPalette::Base);
p->fillRect(option->rect, bg);
if (m->legacy_windows.drawPrimitive(pe, option, p, widget)) {
return;
}
}
if (pe == QStyle::PE_Widget) { // bg for messagebox
const QDialogButtonBox *buttonBox = nullptr;
if (qobject_cast<const QMessageBox *> (widget))
buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox"));
#ifndef QT_NO_INPUTDIALOG
else if (qobject_cast<const QInputDialog *> (widget))
buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox"));
#endif // QT_NO_INPUTDIALOG
if (buttonBox) {
int y = buttonBox->geometry().top();
QRect r(option->rect.x(), y, option->rect.width(), 1);
p->fillRect(r, option->palette.color(QPalette::Light));
r.translate(0, -1);
p->fillRect(r, option->palette.color(QPalette::Dark));
}
return;
}
#ifndef Q_OS_WIN
if (pe == QStyle::PE_PanelTipLabel) {
// ツールチップの背景パネル
p->fillRect(option->rect, QColor(255, 255, 192));
drawFrame(p, option->rect, Qt::black, Qt::black);
return;
}
#endif
// qDebug() << pe;
QProxyStyle::drawPrimitive(pe, option, p, widget);
}
-void DarkStyle::drawControl(ControlElement ce, const QStyleOption *option, QPainter *p, const QWidget *widget) const
+void DarkStyle::drawControl(ControlElement ce, QStyleOption const *option, QPainter *p, QWidget const *widget) const
{
// qDebug() << ce;
bool disabled = !(option->state & State_Enabled);
#ifdef Q_OS_MAC
if (ce == CE_ToolBar) {
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
p->fillRect(x, y + h - 1, w, 1, color(32));
return;
}
#endif
#ifdef Q_OS_LINUX
if (ce == CE_ToolBar) {
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
QColor color = option->palette.color(QPalette::Window);
p->fillRect(x, y + h - 1, w, 1, color);
return;
}
if (ce == CE_PushButtonLabel) {
if (const auto *button = qstyleoption_cast<const QStyleOptionButton *>(option)) {
QRect ir = button->rect;
uint tf = Qt::AlignVCenter | Qt::TextShowMnemonic;
QPoint buttonShift;
if (option->state & State_Sunken) {
buttonShift = QPoint(pixelMetric(PM_ButtonShiftHorizontal, option, widget), proxy()->pixelMetric(PM_ButtonShiftVertical, option, widget));
}
if (proxy()->styleHint(SH_UnderlineShortcut, button, widget)) {
tf |= Qt::TextShowMnemonic;
} else {
tf |= Qt::TextHideMnemonic;
}
if (!button->icon.isNull()) {
//Center both icon and text
QPoint point;
QIcon::Mode mode = button->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
if (mode == QIcon::Normal && button->state & State_HasFocus) {
mode = QIcon::Active;
}
QIcon::State state = QIcon::Off;
if (button->state & State_On) {
state = QIcon::On;
}
QPixmap pixmap = button->icon.pixmap(button->iconSize, mode, state);
int w = pixmap.width();
int h = pixmap.height();
if (!button->text.isEmpty()) {
w += button->fontMetrics.boundingRect(option->rect, tf, button->text).width() + 4;
}
point = QPoint(ir.x() + ir.width() / 2 - w / 2, ir.y() + ir.height() / 2 - h / 2);
if (button->direction == Qt::RightToLeft) {
point.rx() += pixmap.width();
}
p->drawPixmap(visualPos(button->direction, button->rect, point + buttonShift), pixmap);
if (button->direction == Qt::RightToLeft) {
ir.translate(-point.x() - 2, 0);
} else {
ir.translate(point.x() + pixmap.width() + 2, 0);
}
// left-align text if there is
if (!button->text.isEmpty()) {
tf |= Qt::AlignLeft;
}
} else {
tf |= Qt::AlignHCenter;
}
ir.translate(buttonShift);
if (button->features & QStyleOptionButton::HasMenu) {
ir = ir.adjusted(0, 0, -pixelMetric(PM_MenuButtonIndicator, button, widget), 0);
}
drawItemText(p, ir, tf, option->palette, (button->state & State_Enabled), button->text, QPalette::ButtonText);
return;
}
}
if (ce == CE_RadioButton || ce == CE_CheckBox) {
if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
bool isRadio = (ce == CE_RadioButton);
QStyleOptionButton subopt = *btn;
subopt.rect = subElementRect(isRadio ? SE_RadioButtonIndicator : SE_CheckBoxIndicator, btn, widget);
proxy()->drawPrimitive(isRadio ? PE_IndicatorRadioButton : PE_IndicatorCheckBox, &subopt, p, widget);
subopt.rect = subElementRect(isRadio ? SE_RadioButtonContents : SE_CheckBoxContents, btn, widget);
drawControl(isRadio ? CE_RadioButtonLabel : CE_CheckBoxLabel, &subopt, p, widget);
if (btn->state & State_HasFocus) {
QStyleOptionFocusRect fropt;
fropt.QStyleOption::operator=(*btn);
fropt.rect = subElementRect(isRadio ? SE_RadioButtonFocusRect : SE_CheckBoxFocusRect, btn, widget);
drawPrimitive(PE_FrameFocusRect, &fropt, p, widget);
}
return;
}
}
if (ce == CE_RadioButtonLabel || ce == CE_CheckBoxLabel) {
if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
uint alignment = visualAlignment(btn->direction, Qt::AlignLeft | Qt::AlignVCenter);
if (!styleHint(SH_UnderlineShortcut, btn, widget)) {
alignment |= Qt::TextHideMnemonic;
}
QPixmap pix;
QRect textRect = btn->rect;
if (!btn->icon.isNull()) {
pix = btn->icon.pixmap(btn->iconSize, btn->state & State_Enabled ? QIcon::Normal : QIcon::Disabled);
drawItemPixmap(p, btn->rect, alignment, pix);
if (btn->direction == Qt::RightToLeft) {
textRect.setRight(textRect.right() - btn->iconSize.width() - 4);
} else {
textRect.setLeft(textRect.left() + btn->iconSize.width() + 4);
}
}
if (!btn->text.isEmpty()){
drawItemText(p, textRect, alignment | Qt::TextShowMnemonic, btn->palette, btn->state & State_Enabled, btn->text, QPalette::ButtonText);
}
return;
}
}
if (ce == CE_ComboBoxLabel) {
if (const auto *opt = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
QRect editRect = subControlRect(CC_ComboBox, opt, SC_ComboBoxEditField, widget);
uint alignment = Qt::AlignLeft | Qt::AlignVCenter;
if (!styleHint(SH_UnderlineShortcut, opt, widget)) {
alignment |= Qt::TextHideMnemonic;
}
QPixmap pix;
QRect iconRect(editRect);
QIcon icon = opt->currentIcon;
QString text = opt->currentText;
if (!icon.isNull()) {
pix = icon.pixmap(opt->iconSize, opt->state & State_Enabled ? QIcon::Normal : QIcon::Disabled);
drawItemPixmap(p, iconRect, alignment, pix);
if (opt->direction == Qt::RightToLeft) {
editRect.setRight(editRect.right() - opt->iconSize.width() - 4);
} else {
editRect.setLeft(editRect.left() + opt->iconSize.width() + 4);
}
}
if (!text.isEmpty()){
drawItemText(p, editRect, alignment, opt->palette, opt->state & State_Enabled, text, QPalette::ButtonText);
}
return;
}
}
#endif
if (ce == CE_ShapedFrame) {
if (auto const *o = qstyleoption_cast<QStyleOptionFrameV3 const *>(option)) {
int lw = o->lineWidth;
if (lw > 0) {
QRect r = o->rect;
if (o->frameShape == QFrame::Panel || o->frameShape == QFrame::HLine || o->frameShape == QFrame::VLine) {
if (o->state & (State_Sunken | State_Raised)) {
if (r.width() < r.height()) {
if (r.width() > 2) {
r.adjust(0, 0, -1, 0);
}
} else {
if (r.height() > 2) {
r.adjust(0, 0, 0, -1);
}
}
int x = r.x();
int y = r.y();
int w = r.width();
int h = r.height();
QColor color_topleft = o->palette.color(QPalette::Dark);
QColor color_bottomright = o->palette.color(QPalette::Light);
if (o->state & State_Raised) {
std::swap(color_topleft, color_bottomright);
}
int t = lw;
if (o->frameShape == QFrame::HLine) {
y = h / 2 - t;
h = t * 2;
} else if (o->frameShape == QFrame::VLine) {
x = w / 2 - t;
w = t * 2;
}
p->fillRect(x, y, w - t, t, color_topleft);
p->fillRect(x, y + t, t, h - t, color_topleft);
p->fillRect(x + w - t, y, t, h - t, color_bottomright);
p->fillRect(x + t, y + h - t, w - t, t, color_bottomright);
} else {
if (r.width() < r.height()) {
if (lw < r.width()) {
r = QRect(r.x() + (r.width() - lw) / 2, r.y(), lw, r.height());
}
} else {
if (lw < r.height()) {
r = QRect(r.x(), r.y() + (r.height() - lw) / 2, r.width(), lw);
}
}
int x = r.x();
int y = r.y();
int w = r.width();
int h = r.height();
QColor color = o->palette.color(QPalette::Shadow);
p->fillRect(x, y, w, h, color);
}
} else if (o->frameShape == QFrame::Box) {
int x = r.x();
int y = r.y();
int w = r.width();
int h = r.height();
if (w > 1) w--;
if (h > 1) h--;
QColor color_topleft = o->palette.color(QPalette::Dark);
QColor color_bottomright = o->palette.color(QPalette::Light);
if (o->state & State_Raised) {
std::swap(color_topleft, color_bottomright);
}
drawFrame(p, x + 1, y + 1, w, h, color_bottomright, color_bottomright);
drawFrame(p, x, y, w, h, color_topleft, color_topleft);
} else if (o->frameShape == QFrame::StyledPanel) {
QColor color = o->palette.color(QPalette::Midlight);
int x = r.x();
int y = r.y();
int w = r.width();
int h = r.height();
drawFrame(p, x, y, w, h, color, color);
}
}
}
return;
}
if (ce == CE_PushButtonBevel) {
if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
if (btn->features & QStyleOptionButton::Flat) {
// nop
} else {
drawButton(p, option);
}
if (btn->features & QStyleOptionButton::HasMenu) {
int mbiw = 0, mbih = 0;
QRect r = subElementRect(SE_PushButtonContents, option, nullptr);
QStyleOptionButton newBtn = *btn;
r = QRect(r.right() - mbiw - 2, option->rect.top() + (option->rect.height()/2) - (mbih/2), mbiw + 1, mbih + 1);
newBtn.rect = QStyle::visualRect(option->direction, option->rect, r);
drawPrimitive(PE_IndicatorArrowDown, &newBtn, p, widget);
}
}
return;
}
if (ce == CE_MenuBarEmptyArea) {
drawMenuBarBG(p, option, widget);
return;
}
if (ce == CE_MenuBarItem) {
drawMenuBarBG(p, option, widget);
if (option->state & State_Selected) {
drawSelectedMenuFrame(option, p, option->rect, widget);
}
if (const auto *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
QPalette::ColorRole textRole = disabled ? QPalette::Text : QPalette::ButtonText;
QPixmap pix = mbi->icon.pixmap(pixelMetric(PM_SmallIconSize, option, widget), QIcon::Normal);
uint alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
if (!styleHint(SH_UnderlineShortcut, mbi, widget)) {
alignment |= Qt::TextHideMnemonic;
}
if (!pix.isNull()) {
drawItemPixmap(p, mbi->rect, alignment, pix);
} else {
drawItemText(p, mbi->rect, alignment, mbi->palette, mbi->state & State_Enabled, mbi->text, textRole);
}
}
return;
}
if (ce == CE_MenuEmptyArea) {
return;
}
if (ce == CE_MenuItem) {
if (const auto *opt = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
// windows always has a check column, regardless whether we have an icon or not
int checkcol = 25;// / QWindowsXPStylePrivate::devicePixelRatio(widget);
const int gutterWidth = 3;// / QWindowsXPStylePrivate::devicePixelRatio(widget);
QRect rect = option->rect;
bool ignoreCheckMark = false;
if (qobject_cast<const QComboBox*>(widget)) {
{ // bg
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
p->fillRect(x, y, w, h, option->palette.color(QPalette::Light));
}
ignoreCheckMark = true; //ignore the checkmarks provided by the QComboMenuDelegate
}
//draw vertical menu line
if (option->direction == Qt::LeftToRight) {
checkcol += rect.x();
}
int x, y, w, h;
opt->rect.getRect(&x, &y, &w, &h);
int tab = opt->tabWidth;
bool dis = !(opt->state & State_Enabled);
bool checked = (opt->checkType != QStyleOptionMenuItem::NotCheckable) && opt->checked;
bool act = opt->state & State_Selected;
if (opt->menuItemType == QStyleOptionMenuItem::Separator) {
QRect r = option->rect.adjusted(2, 0, -2, 0);
drawGutter(p, r);
return;
}
QRect vCheckRect = visualRect(option->direction, opt->rect, QRect(opt->rect.x(), opt->rect.y(), checkcol - (gutterWidth + opt->rect.x()), opt->rect.height()));
if (act) {
drawSelectedMenuFrame(option, p, option->rect, widget);
}
if (checked && !ignoreCheckMark) {
const qreal boxMargin = 3.5;
const qreal boxWidth = checkcol - 2 * boxMargin;
const int checkColHOffset = windowsItemHMargin + windowsItemFrame - 1;
QRectF checkRectF(option->rect.left() + boxMargin + checkColHOffset, option->rect.center().y() - boxWidth/2 + 1, boxWidth, boxWidth);
QRect checkRect = checkRectF.toRect();
checkRect.setWidth(checkRect.height()); // avoid .toRect() round error results in non-perfect square
checkRect = visualRect(opt->direction, opt->rect, checkRect);
QStyleOptionButton box;
box.QStyleOption::operator=(*option);
box.rect = checkRect;
if (checked) {
box.state |= State_On;
}
drawPrimitive(PE_IndicatorCheckBox, &box, p, widget);
}
if (!ignoreCheckMark) {
if (!opt->icon.isNull()) {
QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal;
if (act && !dis) {
mode = QIcon::Active;
}
QPixmap pixmap;
if (checked) {
pixmap = opt->icon.pixmap(pixelMetric(PM_SmallIconSize, option, widget), mode, QIcon::On);
} else {
pixmap = opt->icon.pixmap(pixelMetric(PM_SmallIconSize, option, widget), mode);
}
double dpr = pixmap.devicePixelRatio();
if (dpr > 0) {
const int pixw = int(pixmap.width() / dpr);
const int pixh = int(pixmap.height() / dpr);
QRect pmr(0, 0, pixw, pixh);
pmr.moveCenter(vCheckRect.center());
p->setPen(opt->palette.text().color());
p->drawPixmap(pmr.topLeft(), pixmap);
}
}
} else {
if (opt->icon.isNull()) {
checkcol = 0;
} else {
checkcol = opt->maxIconWidth;
}
}
p->setPen(opt->palette.buttonText().color());
const QColor textColor = opt->palette.text().color();
if (dis) {
p->setPen(textColor);
}
int xm = windowsItemFrame + checkcol + windowsItemHMargin + (gutterWidth - opt->rect.x()) - 1;
int xpos = opt->rect.x() + xm;
QRect textRect(xpos, y + windowsItemVMargin, w - xm - windowsRightBorder - tab + 1, h - 2 * windowsItemVMargin);
QRect vTextRect = visualRect(option->direction, opt->rect, textRect);
QString s = opt->text;
if (!s.isEmpty()) { // draw text
p->save();
int t = s.indexOf(QLatin1Char('\t'));
int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
if (!styleHint(SH_UnderlineShortcut, opt, widget)) {
text_flags |= Qt::TextHideMnemonic;
}
text_flags |= Qt::AlignLeft;
if (t >= 0) {
QRect vShortcutRect = visualRect(option->direction, opt->rect, QRect(textRect.topRight(), QPoint(opt->rect.right(), textRect.bottom())));
p->drawText(vShortcutRect, text_flags, s.mid(t + 1));
s = s.left(t);
}
QFont font = opt->font;
if (opt->menuItemType == QStyleOptionMenuItem::DefaultItem) {
font.setBold(true);
}
p->setFont(font);
p->setPen(textColor);
p->drawText(vTextRect, text_flags, s.left(t));
p->restore();
}
if (opt->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow
int dim = (h - 2 * windowsItemFrame) / 2;
PrimitiveElement arrow;
arrow = (option->direction == Qt::RightToLeft) ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight;
xpos = x + w - windowsArrowHMargin - windowsItemFrame - dim;
QRect vSubMenuRect = visualRect(option->direction, opt->rect, QRect(xpos, y + h / 2 - dim / 2, dim, dim));
QStyleOptionMenuItem newMI = *opt;
newMI.rect = vSubMenuRect;
newMI.state = dis ? State_None : State_Enabled;
drawPrimitive(arrow, &newMI, p, widget);
}
}
return;
}
if (ce == CE_TabBarTabShape) {
if (const auto *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) {
bool rtlHorTabs = (tab->direction == Qt::RightToLeft && (tab->shape == QTabBar::RoundedNorth || tab->shape == QTabBar::RoundedSouth));
bool selected = tab->state & State_Selected;
bool lastTab = ((!rtlHorTabs && tab->position == QStyleOptionTab::End) || (rtlHorTabs && tab->position == QStyleOptionTab::Beginning));
bool firstTab = ((!rtlHorTabs && tab->position == QStyleOptionTab::Beginning) || (rtlHorTabs && tab->position == QStyleOptionTab::End));
bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab;
bool previousSelected = ((!rtlHorTabs && tab->selectedPosition == QStyleOptionTab::PreviousIsSelected) || (rtlHorTabs && tab->selectedPosition == QStyleOptionTab::NextIsSelected));
bool nextSelected = ((!rtlHorTabs && tab->selectedPosition == QStyleOptionTab::NextIsSelected) || (rtlHorTabs && tab->selectedPosition == QStyleOptionTab::PreviousIsSelected));
int tabBarAlignment = styleHint(SH_TabBar_Alignment, tab, widget);
bool leftAligned = (!rtlHorTabs && tabBarAlignment == Qt::AlignLeft) || (rtlHorTabs && tabBarAlignment == Qt::AlignRight);
bool rightAligned = (!rtlHorTabs && tabBarAlignment == Qt::AlignRight) || (rtlHorTabs && tabBarAlignment == Qt::AlignLeft);
QColor light = tab->palette.light().color();
QColor dark = tab->palette.dark().color();
QColor shadow = tab->palette.shadow().color();
int borderThinkness = pixelMetric(PM_TabBarBaseOverlap, tab, widget);
if (selected) {
borderThinkness /= 2;
}
QRect r2(option->rect);
int x1 = r2.left();
int x2 = r2.right();
int y1 = r2.top();
int y2 = r2.bottom();
switch (tab->shape) {
default:
{
p->save();
QRect rect(tab->rect);
int tabOverlap = onlyOne ? 0 : pixelMetric(PM_TabBarTabOverlap, option, widget);
if (!selected) {
switch (tab->shape) {
case QTabBar::TriangularNorth:
rect.adjust(0, 0, 0, -tabOverlap);
if(!selected) rect.adjust(1, 1, -1, 0);
break;
case QTabBar::TriangularSouth:
rect.adjust(0, tabOverlap, 0, 0);
if(!selected) rect.adjust(1, 0, -1, -1);
break;
case QTabBar::TriangularEast:
rect.adjust(tabOverlap, 0, 0, 0);
if(!selected) rect.adjust(0, 1, -1, -1);
break;
case QTabBar::TriangularWest:
rect.adjust(0, 0, -tabOverlap, 0);
if(!selected) rect.adjust(1, 1, 0, -1);
break;
default:
break;
}
}
p->setPen(QPen(tab->palette.foreground(), 0));
if (selected) {
p->setBrush(tab->palette.background());
} else {
if (widget && widget->parentWidget()) {
p->setBrush(widget->parentWidget()->palette().shadow());
} else {
p->setBrush(tab->palette.shadow());
}
}
int y;
int x;
QPolygon a(10);
switch (tab->shape) {
case QTabBar::TriangularNorth:
case QTabBar::TriangularSouth:
{
a.setPoint(0, 0, -1);
a.setPoint(1, 0, 0);
y = rect.height() - 2;
x = y / 3;
a.setPoint(2, x++, y - 1);
++x;
a.setPoint(3, x++, y++);
a.setPoint(4, x, y);
int i;
int right = rect.width() - 1;
for (i = 0; i < 5; ++i) {
a.setPoint(9 - i, right - a.point(i).x(), a.point(i).y());
}
if (tab->shape == QTabBar::TriangularNorth) {
for (i = 0; i < 10; ++i) {
a.setPoint(i, a.point(i).x(), rect.height() - 1 - a.point(i).y());
}
}
a.translate(rect.left(), rect.top());
p->setRenderHint(QPainter::Antialiasing);
p->translate(0, 0.5);
QPainterPath path;
path.addPolygon(a);
p->drawPath(path);
break;
}
case QTabBar::TriangularEast:
case QTabBar::TriangularWest:
{
a.setPoint(0, -1, 0);
a.setPoint(1, 0, 0);
x = rect.width() - 2;
y = x / 3;
a.setPoint(2, x - 1, y++);
++y;
a.setPoint(3, x++, y++);
a.setPoint(4, x, y);
int i;
int bottom = rect.height() - 1;
for (i = 0; i < 5; ++i) {
a.setPoint(9 - i, a.point(i).x(), bottom - a.point(i).y());
}
if (tab->shape == QTabBar::TriangularWest) {
for (i = 0; i < 10; ++i) {
a.setPoint(i, rect.width() - 1 - a.point(i).x(), a.point(i).y());
}
}
a.translate(rect.left(), rect.top());
p->setRenderHint(QPainter::Antialiasing);
p->translate(0.5, 0);
QPainterPath path;
path.addPolygon(a);
p->drawPath(path);
break;
}
default:
break;
}
p->restore();
}
break;
case QTabBar::RoundedNorth:
{
if (!selected) {
y1 += 2;
x1 += onlyOne || firstTab ? borderThinkness : 0;
x2 -= onlyOne || lastTab ? borderThinkness : 0;
}
p->fillRect(QRect(x1 + 1, y1 + 1, (x2 - x1) - 1, (y2 - y1) - 2), tab->palette.background());
// Delete border
if (selected) {
p->fillRect(QRect(x1,y2-1,x2-x1,1), tab->palette.background());
p->fillRect(QRect(x1,y2,x2-x1,1), tab->palette.background());
}
// Left
if (firstTab || selected || onlyOne || !previousSelected) {
p->setPen(light);
p->drawLine(x1, y1 + 2, x1, y2 - ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness));
p->drawPoint(x1 + 1, y1 + 1);
}
// Top
{
int beg = x1 + (previousSelected ? 0 : 2);
int end = x2 - (nextSelected ? 0 : 2);
p->setPen(light);
p->drawLine(beg, y1, end, y1);
}
// Right
if (lastTab || selected || onlyOne || !nextSelected) {
p->setPen(shadow);
p->drawLine(x2, y1 + 2, x2, y2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness));
p->drawPoint(x2 - 1, y1 + 1);
p->setPen(dark);
p->drawLine(x2 - 1, y1 + 2, x2 - 1, y2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness));
}
break;
}
case QTabBar::RoundedSouth:
{
if (!selected) {
y2 -= 2;
x1 += firstTab ? borderThinkness : 0;
x2 -= lastTab ? borderThinkness : 0;
}
p->fillRect(QRect(x1 + 1, y1 + 2, (x2 - x1) - 1, (y2 - y1) - 1), tab->palette.background());
// Delete border
if (selected) {
p->fillRect(QRect(x1, y1 + 1, (x2 - 1)-x1, 1), tab->palette.background());
p->fillRect(QRect(x1, y1, (x2 - 1)-x1, 1), tab->palette.background());
}
// Left
if (firstTab || selected || onlyOne || !previousSelected) {
p->setPen(light);
p->drawLine(x1, y2 - 2, x1, y1 + ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness));
p->drawPoint(x1 + 1, y2 - 1);
}
// Bottom
{
int beg = x1 + (previousSelected ? 0 : 2);
int end = x2 - (nextSelected ? 0 : 2);
p->setPen(shadow);
p->drawLine(beg, y2, end, y2);
p->setPen(dark);
p->drawLine(beg, y2 - 1, end, y2 - 1);
}
// Right
if (lastTab || selected || onlyOne || !nextSelected) {
p->setPen(shadow);
p->drawLine(x2, y2 - 2, x2, y1 + ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness));
p->drawPoint(x2 - 1, y2 - 1);
p->setPen(dark);
p->drawLine(x2 - 1, y2 - 2, x2 - 1, y1 + ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness));
}
break;
}
case QTabBar::RoundedWest:
{
if (!selected) {
x1 += 2;
y1 += firstTab ? borderThinkness : 0;
y2 -= lastTab ? borderThinkness : 0;
}
p->fillRect(QRect(x1 + 1, y1 + 1, (x2 - x1) - 2, (y2 - y1) - 1), tab->palette.background());
// Delete border
if (selected) {
p->fillRect(QRect(x2 - 1, y1, 1, y2-y1), tab->palette.background());
p->fillRect(QRect(x2, y1, 1, y2-y1), tab->palette.background());
}
// Top
if (firstTab || selected || onlyOne || !previousSelected) {
p->setPen(light);
p->drawLine(x1 + 2, y1, x2 - ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness), y1);
p->drawPoint(x1 + 1, y1 + 1);
}
// Left
{
int beg = y1 + (previousSelected ? 0 : 2);
int end = y2 - (nextSelected ? 0 : 2);
p->setPen(light);
p->drawLine(x1, beg, x1, end);
}
// Bottom
if (lastTab || selected || onlyOne || !nextSelected) {
p->setPen(shadow);
p->drawLine(x1 + 3, y2, x2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness), y2);
p->drawPoint(x1 + 2, y2 - 1);
p->setPen(dark);
p->drawLine(x1 + 3, y2 - 1, x2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness), y2 - 1);
p->drawPoint(x1 + 1, y2 - 1);
p->drawPoint(x1 + 2, y2);
}
break;
}
case QTabBar::RoundedEast:
{
if (!selected) {
x2 -= 2;
y1 += firstTab ? borderThinkness : 0;
y2 -= lastTab ? borderThinkness : 0;
}
p->fillRect(QRect(x1 + 2, y1 + 1, (x2 - x1) - 1, (y2 - y1) - 1), tab->palette.background());
// Delete border
if (selected) {
p->fillRect(QRect(x1 + 1, y1, 1, (y2 - 1)-y1),tab->palette.background());
p->fillRect(QRect(x1, y1, 1, (y2-1)-y1), tab->palette.background());
}
// Top
if (firstTab || selected || onlyOne || !previousSelected) {
p->setPen(light);
p->drawLine(x2 - 2, y1, x1 + ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness), y1);
p->drawPoint(x2 - 1, y1 + 1);
}
// Right
{
int beg = y1 + (previousSelected ? 0 : 2);
int end = y2 - (nextSelected ? 0 : 2);
p->setPen(shadow);
p->drawLine(x2, beg, x2, end);
p->setPen(dark);
p->drawLine(x2 - 1, beg, x2 - 1, end);
}
// Bottom
if (lastTab || selected || onlyOne || !nextSelected) {
p->setPen(shadow);
p->drawLine(x2 - 2, y2, x1 + ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness), y2);
p->drawPoint(x2 - 1, y2 - 1);
p->setPen(dark);
p->drawLine(x2 - 2, y2 - 1, x1 + ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness), y2 - 1);
}
break;
}
}
}
return;
}
if (ce == CE_ProgressBarGroove || ce == CE_ProgressBarContents) {
if (const auto *bar = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) {
QColor color(0, 128, 255);
bool horz = (bar->orientation == Qt::Horizontal);
QString key;
QImage const *image;
if (horz) {
key = "horz";
image = &m->progress_horz;
} else {
key = "vert";
image = &m->progress_vert;
}
p->save();
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
if (ce == CE_ProgressBarContents) {
int len = bar->progress - bar->minimum;
int div = bar->maximum - bar->minimum;
bool inv = bar->invertedAppearance;
if (horz) {
int v = div > 0 ? (w * len / div) : 0;
if (inv) {
x = x + w - v;
}
w = v;
} else {
int v = div > 0 ? (h * len / div) : 0;
if (!inv) {
y = y + h - v;
}
h = v;
}
QRect r(x, y, w, h);
p->setClipRect(r);
p->fillRect(option->rect, color);
}
QPixmap pm;
QSize size(w, h);
key = pixmapkey("progress_bar", key, size, m->base_color);
if (!QPixmapCache::find(key, &pm)) {
QImage im = image->scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
pm = QPixmap::fromImage(im);
QPixmapCache::insert(key, pm);
}
p->setOpacity(0.5);
p->drawPixmap(option->rect, pm, pm.rect());
p->restore();
}
return;
}
if (ce == CE_HeaderSection || ce == CE_HeaderEmptyArea) {
int x = option->rect.x();
int y = option->rect.y();
int w = option->rect.width();
int h = option->rect.height();
QLinearGradient gradient;
gradient.setStart(x, y);
gradient.setFinalStop(x, y + h / 4.0);
gradient.setColorAt(0, option->palette.color(QPalette::Light));
gradient.setColorAt(1, option->palette.color(QPalette::Window));
p->fillRect(x, y, w, h, gradient);
p->fillRect(x, y + h - 1, w, 1, option->palette.color(QPalette::Dark));
if (ce == CE_HeaderSection) {
if (option->state & QStyle::State_MouseOver) {
p->save();
p->fillRect(x, y, w, h, QColor(255, 255, 255, 32));
p->restore();
}
}
return;
}
#ifdef Q_OS_MAC
if (ce == CE_DockWidgetTitle) {
if (const auto *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
const auto *dockWidget = qobject_cast<const QDockWidget *>(widget);
QRect rect = option->rect;
if (dockWidget && dockWidget->isFloating()) {
QProxyStyle::drawControl(ce, option, p, widget);
return;
}
const auto *v2 = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(dwOpt);
bool verticalTitleBar = v2 && v2->verticalTitleBar;
if (verticalTitleBar) {
rect.setSize(rect.size().transposed());
p->translate(rect.left() - 1, rect.top() + rect.width());
p->rotate(-90);
p->translate(-rect.left() + 1, -rect.top());
}
p->setBrush(option->palette.background().color().darker(110));
p->setPen(option->palette.background().color().darker(130));
p->drawRect(rect.adjusted(0, 1, -1, -3));
int buttonMargin = 4;
int mw = proxy()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, dwOpt, widget);
int fw = proxy()->pixelMetric(PM_DockWidgetFrameWidth, dwOpt, widget);
const auto *dw = qobject_cast<const QDockWidget *>(widget);
bool isFloating = dw && dw->isFloating();
QRect r = option->rect.adjusted(0, 2, -1, -3);
QRect titleRect = r;
if (dwOpt->closable) {
QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarCloseButton, dwOpt, widget).actualSize(QSize(10, 10));
titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0);
}
if (dwOpt->floatable) {
QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarMaxButton, dwOpt, widget).actualSize(QSize(10, 10));
titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0);
}
if (isFloating) {
titleRect.adjust(0, -fw, 0, 0);
if (widget && widget->windowIcon().cacheKey() != QApplication::windowIcon().cacheKey()) {
titleRect.adjust(titleRect.height() + mw, 0, 0, 0);
}
} else {
titleRect.adjust(mw, 0, 0, 0);
if (!dwOpt->floatable && !dwOpt->closable) {
titleRect.adjust(0, 0, -mw, 0);
}
}
if (!verticalTitleBar) {
titleRect = visualRect(dwOpt->direction, r, titleRect);
}
if (!dwOpt->title.isEmpty()) {
QString titleText = p->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, verticalTitleBar ? titleRect.height() : titleRect.width());
const int indent = 4;
int align = Qt::AlignRight | Qt::AlignVCenter;
drawItemText(p, rect.adjusted(indent + 1, 1, -indent - 1, -1), align, dwOpt->palette, dwOpt->state & State_Enabled, titleText, QPalette::WindowText);
}
}
return;
}
#endif // Q_OS_MAC
#ifdef Q_OS_LINUX
if (ce == CE_Splitter) {
p->fillRect(option->rect, option->palette.color(QPalette::Window));
return;
}
#endif
// qDebug() << ce;
QProxyStyle::drawControl(ce, option, p, widget);
}
-void DarkStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, QPainter *p, const QWidget *widget) const
+void DarkStyle::drawComplexControl(ComplexControl cc, QStyleOptionComplex const *option, QPainter *p, QWidget const *widget) const
{
// qDebug() << cc;
if (cc == QStyle::CC_ComboBox) {
if (const auto *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
SubControls sub = option->subControls;
if (cmb->editable) {
#define EP_EDITBORDER_NOSCROLL 6
#define EP_EDITBORDER_HVSCROLL 9
if (sub & SC_ComboBoxEditField) {
p->fillRect(option->rect, option->palette.color(QPalette::Dark));
drawFrame(p, option->rect, option->palette.color(QPalette::Shadow), option->palette.color(QPalette::Light));
}
if (sub & SC_ComboBoxArrow) {
QRect subRect = subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget);
QStyleOptionComplex opt = *option;
opt.rect = subRect;
drawToolButton(p, &opt);
opt.rect = QRect(subRect.right() - 4, subRect.y() + subRect.height() / 2 - 1, 2, 2);
drawPrimitive(PE_IndicatorArrowDown, &opt, p, widget);
}
} else {
if (sub & SC_ComboBoxFrame) {
QStyleOptionButton btn;
btn.QStyleOption::operator=(*option);
btn.rect = option->rect.adjusted(-1, -1, 1, 1);
if (sub & SC_ComboBoxArrow) {
btn.features = QStyleOptionButton::HasMenu;
}
drawControl(QStyle::CE_PushButton, &btn, p, widget);
}
}
}
return;
}
if (cc == QStyle::CC_ToolButton) {
QStyle::State flags = option->state;
if (const auto *toolbutton = qstyleoption_cast<const QStyleOptionToolButton *>(option)) {
QRect button, menuarea;
button = subControlRect(cc, toolbutton, SC_ToolButton, widget);
menuarea = subControlRect(cc, toolbutton, SC_ToolButtonMenu, widget);
State bflags = toolbutton->state & ~State_Sunken;
State mflags = bflags;
bool autoRaise = flags & State_AutoRaise;
if (autoRaise) {
if (!(bflags & State_MouseOver) || !(bflags & State_Enabled)) {
bflags &= ~State_Raised;
}
}
if (toolbutton->state & State_Sunken) {
if (toolbutton->activeSubControls & SC_ToolButton) {
bflags |= State_Sunken;
mflags |= State_MouseOver | State_Sunken;
} else if (toolbutton->activeSubControls & SC_ToolButtonMenu) {
mflags |= State_Sunken;
bflags |= State_MouseOver;
}
}
QStyleOption tool(0);
tool.palette = toolbutton->palette;
if (toolbutton->subControls & SC_ToolButton) {
if (flags & (State_Sunken | State_On | State_Raised) || !autoRaise) {
tool.rect = option->rect;
tool.state = bflags;
drawToolButton(p, &tool);
}
}
if (toolbutton->state & State_HasFocus) {
QStyleOptionFocusRect fr;
fr.QStyleOption::operator=(*toolbutton);
fr.rect.adjust(3, 3, -3, -3);
if (toolbutton->features & QStyleOptionToolButton::MenuButtonPopup) {
fr.rect.adjust(0, 0, -pixelMetric(QStyle::PM_MenuButtonIndicator, toolbutton, widget), 0);
}
drawPrimitive(PE_FrameFocusRect, &fr, p, widget);
}
QStyleOptionToolButton label = *toolbutton;
label.state = bflags;
int fw = 2;
if (!autoRaise)
label.state &= ~State_Sunken;
label.rect = button.adjusted(fw, fw, -fw, -fw);
drawControl(CE_ToolButtonLabel, &label, p, widget);
if (toolbutton->subControls & SC_ToolButtonMenu) {
tool.rect = menuarea;
tool.state = mflags;
if (autoRaise) {
drawPrimitive(PE_IndicatorButtonDropDown, &tool, p, widget);
} else {
tool.state = mflags;
menuarea.adjust(-2, 0, 0, 0);
// Draw menu button
if ((bflags & State_Sunken) != (mflags & State_Sunken)){
p->save();
p->setClipRect(menuarea);
tool.rect = option->rect;
drawToolButton(p, &tool);
p->restore();
}
// Draw arrow
p->save();
p->setPen(option->palette.dark().color());
p->drawLine(menuarea.left(), menuarea.top() + 3, menuarea.left(), menuarea.bottom() - 3);
p->setPen(option->palette.light().color());
p->drawLine(menuarea.left() - 1, menuarea.top() + 3, menuarea.left() - 1, menuarea.bottom() - 3);
tool.rect = menuarea.adjusted(2, 3, -2, -1);
drawPrimitive(PE_IndicatorArrowDown, &tool, p, widget);
p->restore();
}
} else if (toolbutton->features & QStyleOptionToolButton::HasMenu) {
int mbi = pixelMetric(PM_MenuButtonIndicator, toolbutton, widget);
QRect ir = toolbutton->rect;
QStyleOptionToolButton newBtn = *toolbutton;
newBtn.rect = QRect(ir.right() + 4 - mbi, ir.height() - mbi + 4, mbi - 5, mbi - 5);
drawPrimitive(PE_IndicatorArrowDown, &newBtn, p, widget);
}
}
return;
}
if (cc == QStyle::CC_ScrollBar) {
bool ishorz = (option->state & State_Horizontal);
auto *tx = const_cast<ScrollBarTextures *>(ishorz ? &m->hsb : &m->vsb);
int extent = (ishorz ? tx->slider.im_normal.height() : tx->slider.im_normal.width()) - 2;
auto DrawNinePatchImage2 = [&](QImage const &image, QRect const &r){
int w, h;
if (ishorz) {
h = extent;
int d = r.height();
w = (d == 0) ? 0 : (h * r.width() / d);
} else {
w = extent;
int d = r.width();
h = (d == 0) ? 0 : (w * r.height() / d);
}
drawNinePatchImage(p, image, r, w, h);
};
p->fillRect(option->rect, color(64));
p->setRenderHint(QPainter::Antialiasing);
{
#ifdef Q_OS_MAC
QRect r = option->rect;
#else
QRect r1 = subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget);
QRect r2 = subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget);
QRect r = r1.united(r2);
#endif
DrawNinePatchImage2(tx->page_bg, r);
}
#ifndef Q_OS_MAC // macOSのスクロールバーには、ラインステップボタンが無い
{
QRect r = subControlRect(CC_ScrollBar, option, SC_ScrollBarSubLine, widget);
DrawNinePatchImage2(tx->page_bg, r);
}
{
QRect r = subControlRect(CC_ScrollBar, option, SC_ScrollBarAddLine, widget);
DrawNinePatchImage2(tx->page_bg, r);
}
auto DrawAddSubButton = [&](ButtonImages const &ims, SubControl subctl){
QRect r = subControlRect(CC_ScrollBar, option, subctl, widget);
QPixmap pm;;
if (!(option->activeSubControls & subctl)) {
pm = pixmapFromImage(ims.im_normal, r.size());
p->drawPixmap(r.topLeft(), pm);
} else {
pm = pixmapFromImage(ims.im_hover, r.size());
p->drawPixmap(r.topLeft(), pm);
}
};
DrawAddSubButton(tx->sub_line, SC_ScrollBarSubLine);
DrawAddSubButton(tx->add_line, SC_ScrollBarAddLine);
#endif
{
QRect r = subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
int w, h;
if (ishorz) {
h = extent;
int d = r.height();
w = (d == 0) ? 0 : (h * r.width() / d);
} else {
w = extent;
int d = r.width();
h = (d == 0) ? 0 : (w * r.height() / d);
}
#ifdef Q_OS_MAC // macだとズレて見えるので調整する
if (ishorz) {
r = r.translated(1, 0);
} else {
r = r.translated(0, 1);
}
#endif
if (!(option->activeSubControls & SC_ScrollBarSlider)) {
DrawNinePatchImage2(tx->slider.im_normal, r);
} else {
DrawNinePatchImage2(tx->slider.im_hover, r);
}
}
return;
}
if (cc == CC_GroupBox) {
if (const auto *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(option)) {
QRect textRect = subControlRect(CC_GroupBox, option, SC_GroupBoxLabel, widget);
QRect checkBoxRect = subControlRect(CC_GroupBox, option, SC_GroupBoxCheckBox, widget);
if (groupBox->subControls & QStyle::SC_GroupBoxFrame) {
QStyleOptionFrame frame;
frame.QStyleOption::operator=(*groupBox);
frame.features = groupBox->features;
frame.lineWidth = groupBox->lineWidth;
frame.midLineWidth = groupBox->midLineWidth;
frame.rect = subControlRect(CC_GroupBox, option, SC_GroupBoxFrame, widget);
p->save();
QRegion region(groupBox->rect);
if (!groupBox->text.isEmpty()) {
bool ltr = groupBox->direction == Qt::LeftToRight;
QRect finalRect;
if (groupBox->subControls & QStyle::SC_GroupBoxCheckBox) {
finalRect = checkBoxRect.united(textRect);
finalRect.adjust(ltr ? -4 : 0, 0, ltr ? 0 : 4, 0);
} else {
finalRect = textRect;
}
region -= finalRect;
}
p->setClipRegion(region);
drawPrimitive(PE_FrameGroupBox, &frame, p, widget);
p->restore();
}
// Draw title
if ((groupBox->subControls & QStyle::SC_GroupBoxLabel) && !groupBox->text.isEmpty()) {
QColor textColor = groupBox->textColor;
if (textColor.isValid()) {
p->setPen(textColor);
}
int alignment = int(groupBox->textAlignment);
if (!styleHint(QStyle::SH_UnderlineShortcut, option, widget)) {
alignment |= Qt::TextHideMnemonic;
}
alignment |= Qt::TextShowMnemonic;
alignment |= Qt::AlignHCenter;
drawItemText(p, textRect, alignment, groupBox->palette, groupBox->state & State_Enabled, groupBox->text, QPalette::WindowText);
if (groupBox->state & State_HasFocus) {
QStyleOptionFocusRect fropt;
fropt.QStyleOption::operator=(*groupBox);
fropt.rect = textRect;
drawPrimitive(PE_FrameFocusRect, &fropt, p, widget);
}
}
// Draw checkbox
if (groupBox->subControls & SC_GroupBoxCheckBox) {
QStyleOptionButton box;
box.QStyleOption::operator=(*groupBox);
box.rect = checkBoxRect;
drawPrimitive(PE_IndicatorCheckBox, &box, p, widget);
}
}
return;
}
if (cc == CC_Slider) {
if (const auto *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
QRect groove = subControlRect(CC_Slider, option, SC_SliderGroove, widget);
QRect handle = subControlRect(CC_Slider, option, SC_SliderHandle, widget);
bool horizontal = slider->orientation == Qt::Horizontal;
bool ticksAbove = slider->tickPosition & QSlider::TicksAbove;
bool ticksBelow = slider->tickPosition & QSlider::TicksBelow;
QPixmap cache;
QBrush oldBrush = p->brush();
QPen oldPen = p->pen();
QColor shadowAlpha(Qt::black);
shadowAlpha.setAlpha(10);
if ((option->subControls & SC_SliderGroove) && groove.isValid()) {
QRect rect(0, 0, groove.width(), groove.height());
QString key = pixmapkey("slider_groove", horizontal ? "horz" : "vert", rect.size(), m->base_color);
QRectF grooveRect;
double r = std::min(groove.width(), groove.height()) / 2.0;
{
double n = r * 3 / 4;
grooveRect = QRectF(rect).adjusted(n, n, -n, -n);
r -= n;
}
// draw background groove
QPixmap cache;
if (!QPixmapCache::find(key, cache)) {
cache = QPixmap(rect.size());
cache.fill(Qt::transparent);
QPainter groovePainter(&cache);
groovePainter.setRenderHint(QPainter::Antialiasing, true);
groovePainter.translate(0.5, 0.5);
QLinearGradient gradient;
if (horizontal) {
gradient.setStart(grooveRect.center().x(), grooveRect.top());
gradient.setFinalStop(grooveRect.center().x(), grooveRect.bottom());
} else {
gradient.setStart(grooveRect.left(), grooveRect.center().y());
gradient.setFinalStop(grooveRect.right(), grooveRect.center().y());
}
groovePainter.setPen(Qt::NoPen);
gradient.setColorAt(0, color(32));
gradient.setColorAt(1, color(128));
groovePainter.setBrush(gradient);
groovePainter.drawRoundedRect(grooveRect, r, r);
groovePainter.end();
QPixmapCache::insert(key, cache);
}
p->drawPixmap(groove.topLeft(), cache);
}
if (option->subControls & SC_SliderTickmarks) {
int tickSize = pixelMetric(PM_SliderTickmarkOffset, option, widget);
int available = pixelMetric(PM_SliderSpaceAvailable, slider, widget);
int interval = slider->tickInterval;
if (interval <= 0) {
interval = slider->singleStep;
if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval, available) - QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, 0, available) < 3) {
interval = slider->pageStep;
}
}
if (interval <= 0) {
interval = 1;
}
int v = slider->minimum;
int len = pixelMetric(PM_SliderLength, slider, widget);
while (v <= slider->maximum + 1) {
if (v == slider->maximum + 1 && interval == 1) break;
const int v2 = qMin(v, slider->maximum);
int pos = sliderPositionFromValue(slider->minimum, slider->maximum,
v2, (horizontal
? slider->rect.width()
: slider->rect.height()) - len,
slider->upsideDown) + len / 2;
int extra = 2 - ((v2 == slider->minimum || v2 == slider->maximum) ? 1 : 0);
if (horizontal) {
if (ticksAbove) {
p->setPen(color(128));
p->drawLine(pos, slider->rect.top() + extra, pos, slider->rect.top() + tickSize);
}
if (ticksBelow) {
p->setPen(color(0));
p->drawLine(pos, slider->rect.bottom() - extra, pos, slider->rect.bottom() - tickSize);
}
} else {
if (ticksAbove) {
p->setPen(color(128));
p->drawLine(slider->rect.left() + extra, pos, slider->rect.left() + tickSize, pos);
}
if (ticksBelow) {
p->setPen(color(0));
p->drawLine(slider->rect.right() - extra, pos, slider->rect.right() - tickSize, pos);
}
}
// in the case where maximum is max int
int nextInterval = v + interval;
if (nextInterval < v) break;
v = nextInterval;
}
}
// draw handle
if ((option->subControls & SC_SliderHandle) ) {
QRect pixmapRect(0, 0, handle.width(), handle.height());
QRect r = pixmapRect.adjusted(0, 0, -1, -1);
QPainterPath path;
path.addEllipse(r);
QString handlePixmapName = pixmapkey(QLatin1String("slider_handle"), "", handle.size(), m->base_color);
if (!QPixmapCache::find(handlePixmapName, cache)) {
cache = QPixmap(handle.size());
cache.fill(Qt::transparent);
QPainter handlePainter(&cache);
handlePainter.setRenderHint(QPainter::Antialiasing, true);
handlePainter.translate(0.5, 0.5);
QLinearGradient gradient;
gradient.setStart(pixmapRect.topLeft());
gradient.setFinalStop(pixmapRect.bottomRight());
gradient.setColorAt(0, color(192));
gradient.setColorAt(1, QColor(0, 0, 0));
handlePainter.save();
handlePainter.setClipPath(path);
handlePainter.fillRect(r, gradient);
QColor highlight_color = colorize(color(160).rgb(), 192, 255);
handlePainter.setPen(QPen(highlight_color, 2));
handlePainter.setBrush(Qt::NoBrush);
handlePainter.drawEllipse(r.adjusted(0, 0, 2, 2));
handlePainter.setPen(QPen(QColor(0, 0, 0), 2));
handlePainter.setBrush(Qt::NoBrush);
handlePainter.drawEllipse(r.adjusted(-2, -2, 0, 0));
handlePainter.restore();
handlePainter.end();
QPixmapCache::insert(handlePixmapName, cache);
}
QPoint topleft = handle.topLeft();
p->drawPixmap(topleft, cache);
if ((option->state & State_HasFocus) && (option->state & State_KeyboardFocusChange)) {
p->save();
p->setClipPath(path.translated(topleft));
p->fillRect(handle, QColor(255, 255, 255, 32));
p->restore();
}
}
p->setBrush(oldBrush);
p->setPen(oldPen);
}
return;
}
// qDebug() << cc;
QProxyStyle::drawComplexControl(cc, option, p, widget);
}
diff --git a/src/darktheme/DarkStyle.h b/src/darktheme/DarkStyle.h
index 0e889ab..4382b85 100644
--- a/src/darktheme/DarkStyle.h
+++ b/src/darktheme/DarkStyle.h
@@ -1,59 +1,59 @@
#ifndef DARKSTYLE_H
#define DARKSTYLE_H
#include <QPainter>
#include <QProxyStyle>
class DarkStyle : public QProxyStyle {
public:
private:
struct Private;
Private *m;
class ButtonImages {
public:
QImage im_normal;
QImage im_hover;
};
struct ScrollBarTextures {
QImage page_bg;
ButtonImages sub_line;
ButtonImages add_line;
ButtonImages slider;
};
QImage colorizeImage(QImage image);
QImage loadColorizedImage(QString const &path, QString const &role = QString());
ButtonImages generateButtonImages(QString const &path);
QImage generateHoverImage(const QImage &source);
QPixmap pixmapFromImage(QImage const &image, QSize size) const;
void loadImages();
QColor colorForSelectedFrame(QStyleOption const *opt) const;
QColor colorForItemView(QStyleOption const *opt) const;
void drawNinePatchImage(QPainter *p, QImage const &image, QRect const &r, int w, int h) const;
void drawGutter(QPainter *p, QRect const &r) const;
- void drawSelectedMenuFrame(const QStyleOption *option, QPainter *p, QRect rect, QWidget const *widget, bool deep = false) const;
+ void drawSelectedMenuFrame(QStyleOption const *option, QPainter *p, QRect rect, QWidget const *widget, bool deep = false) const;
void drawButton(QPainter *p, QStyleOption const *option) const;
void drawToolButton(QPainter *p, QStyleOption const *option) const;
- void drawMenuBarBG(QPainter *p, const QStyleOption *option, const QWidget *widget) const;
+ void drawMenuBarBG(QPainter *p, QStyleOption const *option, QWidget const *widget) const;
QColor color(int level, int alpha = 255) const;
public:
DarkStyle(const QColor &base_color = QColor());
~DarkStyle() override;
QColor getBaseColor();
void setBaseColor(const QColor &color);
void setScrollBarExtent(int n);
void polish(QPalette &palette) override;
- int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const override;
- QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *option, SubControl sc, const QWidget *widget) const override;
- void drawPrimitive(PrimitiveElement pe, const QStyleOption *option, QPainter *p, const QWidget *widget) const override;
- void drawControl(ControlElement ce, const QStyleOption *option, QPainter *p, const QWidget *widget) const override;
- void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, QPainter *p, const QWidget *widget) const override;
+ int pixelMetric(PixelMetric metric, QStyleOption const *option, QWidget const *widget) const override;
+ QRect subControlRect(ComplexControl cc, QStyleOptionComplex const *option, SubControl sc, QWidget const *widget) const override;
+ void drawPrimitive(PrimitiveElement pe, QStyleOption const *option, QPainter *p, QWidget const *widget) const override;
+ void drawControl(ControlElement ce, QStyleOption const *option, QPainter *p, QWidget const *widget) const override;
+ void drawComplexControl(ComplexControl cc, QStyleOptionComplex const *option, QPainter *p, QWidget const *widget) const override;
};
#endif // DARKSTYLE_H
diff --git a/src/darktheme/StandardStyle.cpp b/src/darktheme/StandardStyle.cpp
index 2426bc5..48674a4 100644
--- a/src/darktheme/StandardStyle.cpp
+++ b/src/darktheme/StandardStyle.cpp
@@ -1,11 +1,11 @@
#include "StandardStyle.h"
-void StandardStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
+void StandardStyle::drawPrimitive(PrimitiveElement element, QStyleOption const *option, QPainter *painter, QWidget const *widget) const
{
if (element == QStyle::PE_IndicatorBranch) {
if (legacy_windows_.drawPrimitive(element, option, painter, widget)) {
return;
}
}
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
diff --git a/src/darktheme/StandardStyle.h b/src/darktheme/StandardStyle.h
index 1d877ce..2f43269 100644
--- a/src/darktheme/StandardStyle.h
+++ b/src/darktheme/StandardStyle.h
@@ -1,20 +1,20 @@
#ifndef STANDARDSTYLE_H
#define STANDARDSTYLE_H
#include "TraditionalWindowsStyleTreeControl.h"
#include <QProxyStyle>
class StandardStyle : public QProxyStyle {
private:
TraditionalWindowsStyleTreeControl legacy_windows_;
public:
StandardStyle()
: QProxyStyle(0)
{
}
- void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const;
+ void drawPrimitive(PrimitiveElement element, QStyleOption const *option, QPainter *painter, QWidget const *widget = 0) const;
};
#endif // STANDARDSTYLE_H
diff --git a/src/darktheme/TraditionalWindowsStyleTreeControl.cpp b/src/darktheme/TraditionalWindowsStyleTreeControl.cpp
index 86b8d13..caff928 100644
--- a/src/darktheme/TraditionalWindowsStyleTreeControl.cpp
+++ b/src/darktheme/TraditionalWindowsStyleTreeControl.cpp
@@ -1,87 +1,87 @@
#include "TraditionalWindowsStyleTreeControl.h"
#include <QStyleOption>
#include <QPainter>
#include <QDebug>
static unsigned char plus_image[9][9] = {
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80,
0x80, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x80,
0x80, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x80,
0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80,
0x80, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x80,
0x80, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x80,
0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
};
static unsigned char minus_image[9][9] = {
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80,
0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80,
0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80,
0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80,
0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80,
0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80,
0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
};
TraditionalWindowsStyleTreeControl::TraditionalWindowsStyleTreeControl()
{
// create brush
{
QImage img(2, 2, QImage::Format_RGB32);
QRgb *p;
QRgb t = qRgb(0xff, 0xff, 0xff);
QRgb u = qRgb(0x80, 0x80, 0x80);
p = reinterpret_cast<QRgb *>(img.scanLine(0));
p[0] = u;
p[1] = t;
p = reinterpret_cast<QRgb *>(img.scanLine(1));
p[0] = t;
p[1] = u;
br_branch = QBrush(img);
}
// create open/close buttons
QImage img_plus(9, 9, QImage::Format_RGB32);
QImage img_minus(9, 9, QImage::Format_RGB32);
for (int y = 0; y < 9; y++) {
for (int x = 0; x < 9; x++) {
unsigned char v;
v = plus_image[y][x];
reinterpret_cast<QRgb *>(img_plus.scanLine(y))[x] = qRgb(v, v, v);
v = minus_image[y][x];
reinterpret_cast<QRgb *>(img_minus.scanLine(y))[x] = qRgb(v, v, v);
}
}
pm_plus = QPixmap::fromImage(img_plus);
pm_minus = QPixmap::fromImage(img_minus);
}
-bool TraditionalWindowsStyleTreeControl::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *) const
+bool TraditionalWindowsStyleTreeControl::drawPrimitive(QStyle::PrimitiveElement element, QStyleOption const *option, QPainter *painter, QWidget const *) const
{
if (element == QStyle::PE_IndicatorBranch) {
int ox = option->rect.x() + option->rect.width() / 2;
int oy = option->rect.y() + option->rect.height() / 2;
if (option->state & QStyle::State_Item) {
painter->fillRect(ox, oy, option->rect.right() - ox + 1, 1, br_branch);
}
if (option->state & QStyle::State_Sibling) {
painter->fillRect(ox, oy, 1, option->rect.bottom() - oy + 1, br_branch);
}
if (option->state & (QStyle::State_Open | QStyle::State_Children | QStyle::State_Item | QStyle::State_Sibling)) {
painter->fillRect(ox, option->rect.y(), 1, oy - option->rect.y(), br_branch);
}
if (option->state & QStyle::State_Children) {
if (option->state & QStyle::State_Open) {
painter->drawPixmap(ox - 4, oy - 4, pm_minus);
} else {
painter->drawPixmap(ox - 4, oy - 4, pm_plus);
}
}
return true;
}
return false;
}
diff --git a/src/darktheme/TraditionalWindowsStyleTreeControl.h b/src/darktheme/TraditionalWindowsStyleTreeControl.h
index 2acbf7a..bee409d 100644
--- a/src/darktheme/TraditionalWindowsStyleTreeControl.h
+++ b/src/darktheme/TraditionalWindowsStyleTreeControl.h
@@ -1,21 +1,21 @@
#ifndef TRADITIONALWINDOWSSTYLETREECONTROL_H
#define TRADITIONALWINDOWSSTYLETREECONTROL_H
#include <QBrush>
#include <QPixmap>
#include <QStyle>
class QPainter;
class QWidget;
class TraditionalWindowsStyleTreeControl {
private:
QBrush br_branch;
QPixmap pm_plus;
QPixmap pm_minus;
public:
TraditionalWindowsStyleTreeControl();
- bool drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const;
+ bool drawPrimitive(QStyle::PrimitiveElement element, QStyleOption const *option, QPainter *painter, QWidget const *widget = 0) const;
};
#endif // TRADITIONALWINDOWSSTYLETREECONTROL_H
diff --git a/src/main.cpp b/src/main.cpp
index 4b159aa..69c9034 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,144 +1,146 @@
#include "darktheme/DarkStyle.h"
#include "ApplicationGlobal.h"
#include "MainWindow.h"
#include "MySettings.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include "main.h"
#include "platform.h"
#include "webclient.h"
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QMessageBox>
#include <QProxyStyle>
#include <QStandardPaths>
#include <QTranslator>
#include <string>
ApplicationGlobal *global = nullptr;
ApplicationSettings ApplicationSettings::defaultSettings()
{
ApplicationSettings s;
s.proxy_server = "http://squid:3128/";
return s;
}
static bool isHighDpiScalingEnabled()
{
MySettings s;
s.beginGroup("UI");
QVariant v = s.value("EnableHighDpiScaling");
return v.isNull() || v.toBool();
}
int main(int argc, char *argv[])
{
putenv((char *)"UNICODEMAP_JP=cp932");
ApplicationGlobal g;
global = &g;
global->organization_name = ORGANIZATION_NAME;
global->application_name = APPLICATION_NAME;
global->generic_config_dir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
global->app_config_dir = global->generic_config_dir / global->organization_name / global->application_name;
global->config_file_path = joinpath(global->app_config_dir, global->application_name + ".ini");
if (!QFileInfo(global->app_config_dir).isDir()) {
QDir().mkpath(global->app_config_dir);
}
if (isHighDpiScalingEnabled()){
#if (QT_VERSION < QT_VERSION_CHECK(5, 6, 0))
qDebug() << "High DPI scaling is not supported";
#else
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
}
QApplication a(argc, argv);
QApplication::setOrganizationName(global->organization_name);
QApplication::setApplicationName(global->application_name);
+ qRegisterMetaType<RepositoryItem>("RepositoryItem");
+
{
MySettings s;
s.beginGroup("UI");
global->language_id = s.value("Language").toString();
global->theme_id = s.value("Theme").toString();
if (global->theme_id.compare("dark", Qt::CaseInsensitive) == 0) {
global->theme = createDarkTheme();
} else {
global->theme = createStandardTheme();
}
s.endGroup();
}
QApplication::setStyle(global->theme->newStyle());
if (QApplication::queryKeyboardModifiers() & Qt::ShiftModifier) {
global->start_with_shift_key = true;
}
WebClient::initialize();
bool f_open_here = false;
QStringList args;
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg[0] == '-') {
if (arg == "--open-here") {
f_open_here = true;
}
} else {
args.push_back(QString::fromStdString(arg));
}
}
if (global->app_config_dir.isEmpty()) {
QMessageBox::warning(nullptr, qApp->applicationName(), "Preparation of data storage folder failed.");
return 1;
}
QTranslator translator;
{
if (global->language_id.isEmpty() || global->language_id == "en") {
// thru
} else {
#if defined(Q_OS_MACX)
QString path = "../Resources/Guitar_" + global->language_id;
#else
QString path = "Guitar_" + global->language_id;
#endif
translator.load(path, QApplication::applicationDirPath());
QApplication::installTranslator(&translator);
}
}
MainWindow w;
global->panel_bg_color = w.palette().color(QPalette::Background);
w.setWindowIcon(QIcon(":/image/guitar.png"));
w.show();
w.shown();
if (f_open_here) {
QString dir = QDir::current().absolutePath();
w.autoOpenRepository(dir);
} else if (args.size() == 1) {
QString dir = args[0] / QString();
if (dir.startsWith("./") || dir.startsWith(".\\")) {
dir = QDir::current().absolutePath() / dir.mid(2);
}
QFileInfo fi(dir);
if (fi.isDir()) {
dir = fi.absolutePath();
w.autoOpenRepository(dir);
}
}
return QApplication::exec();
}
diff --git a/src/texteditor/AbstractCharacterBasedApplication.cpp b/src/texteditor/AbstractCharacterBasedApplication.cpp
index f78fc6f..997731b 100644
--- a/src/texteditor/AbstractCharacterBasedApplication.cpp
+++ b/src/texteditor/AbstractCharacterBasedApplication.cpp
@@ -1,2585 +1,2585 @@
#include <memory>
#include <memory>
#include "AbstractCharacterBasedApplication.h"
#include "UnicodeWidth.h"
#include "unicode.h"
#include <QApplication>
#include <QClipboard>
#include <QDebug>
#include <QFile>
#include <QTextCodec>
#include <memory>
using WriteMode = AbstractCharacterBasedApplication::WriteMode;
using FormattedLine = AbstractCharacterBasedApplication::FormattedLine;
class EsccapeSequence {
private:
int offset = 0;
unsigned char data[100];
int color_fg = -1;
int color_bg = -1;
public:
bool isActive() const
{
return offset > 0;
}
void write(char c)
{
if (c == 0x1b) {
data[0] = c;
offset = 1;
return;
}
data[offset] = c;
if (offset > 0) {
if (c == 'm') {
if (data[1] == '[' && isdigit(data[2]) && isdigit(data[3])) {
data[offset] = 0;
if (data[2] == '3') {
color_fg = atoi((char const *)data + 3);
if (color_fg == 9) {
color_fg = -1;
}
}
if (data[2] == '4') {
color_bg = atoi((char const *)data + 3);
if (color_bg == 9) {
color_bg = -1;
}
}
}
offset = 0;
return;
}
if (offset + 1 < (int)sizeof(data)) {
offset++;
}
}
}
int fg_color_code() const
{
return color_fg == 9 ? -1 : color_fg;
}
int bg_color_code() const
{
return color_bg == 9 ? -1 : color_bg;
}
};
struct AbstractCharacterBasedApplication::Private {
bool is_changed = false;
bool is_quit_enabled = false;
bool is_open_enabled = false;
bool is_save_enabled = false;
bool is_toggle_selection_anchor_enabled = true;
bool is_read_only = false;
bool is_terminal_mode = false;
bool is_cursor_visible = true;
State state = State::Normal;
int header_line = 1;
int footer_line = 1;
int screen_width = 80;
int screen_height = 24;
bool auto_layout = false;
QString recently_used_path;
bool show_line_number = true;
int left_margin = AbstractCharacterBasedApplication::LEFT_MARGIN;
QString dialog_title;
QString dialog_value;
std::vector<Character> screen;
std::vector<uint8_t> line_flags;
int parsed_row_index = -1;
int parsed_col_index = -1;
bool parsed_for_edit = false;
QByteArray parsed_line;
std::vector<uint32_t> prepared_current_line;
QList<Document::CharAttr_> syntax_table;
bool dialog_mode = false;
DialogHandler dialog_handler;
bool is_painting_suppressed = false;
int valid_line_index = -1;
int line_margin = 3;
WriteMode write_mode = WriteMode::Insert;
QTextCodec *text_codec = nullptr;
Qt::KeyboardModifiers keyboard_modifiers = Qt::KeyboardModifier::NoModifier;
bool ctrl_modifier = false;
bool shift_modifier = false;
EsccapeSequence escape_sequence;
bool cursor_moved_by_mouse = false;
};
AbstractCharacterBasedApplication::AbstractCharacterBasedApplication()
: m(new Private)
{
}
AbstractCharacterBasedApplication::~AbstractCharacterBasedApplication()
{
delete m;
}
void AbstractCharacterBasedApplication::setModifierKeys(Qt::KeyboardModifiers keymod)
{
m->keyboard_modifiers = keymod;
m->ctrl_modifier = m->keyboard_modifiers & Qt::ControlModifier;
m->shift_modifier = m->keyboard_modifiers & Qt::ShiftModifier;
}
void AbstractCharacterBasedApplication::clearShiftModifier()
{
m->shift_modifier = false;
}
bool AbstractCharacterBasedApplication::isControlModifierPressed() const
{
return m->ctrl_modifier;
}
bool AbstractCharacterBasedApplication::isShiftModifierPressed() const
{
return m->shift_modifier;
}
void AbstractCharacterBasedApplication::setTextCodec(QTextCodec *codec)
{
m->text_codec = codec;
clearParsedLine();
invalidateArea();
makeBuffer();
}
void AbstractCharacterBasedApplication::setAutoLayout(bool f)
{
m->auto_layout = f;
layoutEditor();
}
void AbstractCharacterBasedApplication::showHeader(bool f)
{
m->header_line = f ? 1 : 0;
layoutEditor();
}
void AbstractCharacterBasedApplication::showFooter(bool f)
{
m->footer_line = f ? 1 : 0;
layoutEditor();
}
void AbstractCharacterBasedApplication::showLineNumber(bool show, int left_margin)
{
m->show_line_number = show;
m->left_margin = left_margin;
}
void AbstractCharacterBasedApplication::setCursorVisible(bool show)
{
m->is_cursor_visible = show;
}
bool AbstractCharacterBasedApplication::isCursorVisible()
{
return m->is_cursor_visible;
}
void AbstractCharacterBasedApplication::retrieveLastText(std::vector<char> *out, int maxlen) const
{
const_cast<AbstractCharacterBasedApplication *>(this)->document()->retrieveLastText(out, maxlen);
}
bool AbstractCharacterBasedApplication::isChanged() const
{
return m->is_changed;
}
void AbstractCharacterBasedApplication::setChanged(bool f)
{
m->is_changed = f;
}
int AbstractCharacterBasedApplication::leftMargin_() const
{
return m->left_margin;
}
void AbstractCharacterBasedApplication::setRecentlyUsedPath(QString const &path)
{
m->recently_used_path = path;
}
QString AbstractCharacterBasedApplication::recentlyUsedPath()
{
return m->recently_used_path;
}
std::vector<AbstractCharacterBasedApplication::Character> *AbstractCharacterBasedApplication::screen()
{
return &m->screen;
}
const std::vector<AbstractCharacterBasedApplication::Character> *AbstractCharacterBasedApplication::screen() const
{
return &m->screen;
}
std::vector<uint8_t> *AbstractCharacterBasedApplication::line_flags()
{
return &m->line_flags;
}
void AbstractCharacterBasedApplication::makeBuffer()
{
int w = screenWidth();
int h = screenHeight();
int size = w * h;
m->screen.resize(size);
std::fill(m->screen.begin(), m->screen.end(), Character());
m->line_flags.resize(h);
}
void AbstractCharacterBasedApplication::layoutEditor()
{
makeBuffer();
editor_cx->viewport_org_x = leftMargin_();
editor_cx->viewport_org_y = m->header_line;
editor_cx->viewport_width = screenWidth() - cx()->viewport_org_x;
editor_cx->viewport_height = screenHeight() - (m->header_line + m->footer_line);
}
void AbstractCharacterBasedApplication::initEditor()
{
editor_cx = std::make_shared<TextEditorContext>();
layoutEditor();
}
bool AbstractCharacterBasedApplication::isLineNumberVisible() const
{
return m->show_line_number;
}
int AbstractCharacterBasedApplication::charWidth(uint32_t c)
{
return UnicodeWidth::width(UnicodeWidth::type(c));
}
QList<FormattedLine> AbstractCharacterBasedApplication::formatLine(Document::Line const &line, int tab_span, int anchor_a, int anchor_b) const
{
QByteArray ba;
if (m->text_codec) {
ba = m->text_codec->toUnicode(line.text).toUtf8();
} else {
ba = line.text;
}
std::vector<ushort> vec;
vec.reserve(ba.size() + 100);
size_t len = ba.size();
QList<FormattedLine> res;
int col = 0;
int col_start = col;
bool flag_a = false;
bool flag_b = false;
auto Color = [&](size_t offset, size_t *next_offset){
int i = findSyntax(&m->syntax_table, offset);
if (i < m->syntax_table.size() && m->syntax_table[i].offset <= offset) {
if (next_offset) {
if (i + 1 < m->syntax_table.size()) {
*next_offset = m->syntax_table[i + 1].offset;
} else {
*next_offset = -1;
}
}
static int color[] = {
0x000000,
0x0000ff,
0x00ff00,
0x00ffff,
0xff0000,
0xff00ff,
0xffff00,
0xffffff,
};
i = m->syntax_table[i].color;
if (i >= 0 && i < 8) {
return color[i];
}
}
if (next_offset) {
*next_offset = -1;
}
return 0;
};
auto Flush = [&](size_t offset, size_t *next_offset){
if (!vec.empty()) {
int atts = 0;
atts |= Color(offset, next_offset);
if (anchor_a >= 0 || anchor_b >= 0) {
if ((anchor_a < 0 || col_start >= anchor_a) && (anchor_b == -1 || col_start < anchor_b)) {
atts |= FormattedLine::Selected;
}
}
ushort const *left = &vec[0];
ushort const *right = left + vec.size();
while (left < right && (right[-1] == '\r' || right[-1] == '\n')) right--;
if (left < right) {
res.push_back(FormattedLine(QString::fromUtf16(left, right - left), atts));
}
vec.clear();
} else {
*next_offset = -1;
}
col_start = col;
};
size_t offset = 0;
size_t next_offset = -1;
if (len > 0) {
{
size_t o = line.byte_offset;
int i = findSyntax(&m->syntax_table, o);
if (i < m->syntax_table.size() && m->syntax_table[i].offset <= o) {
if (i + 1 < m->syntax_table.size()) {
o = m->syntax_table[i + 1].offset;
if (o != (size_t)-1) {
next_offset = o;
}
}
}
}
utf8 u8(ba.data(), len);
u8.to_utf32([&](uint32_t c){
if (line.byte_offset + u8.offset() == next_offset) {
Flush(line.byte_offset + offset, &next_offset);
offset += u8.offset();
}
if (c == '\t') {
do {
vec.push_back(' ');
col++;
} while (col % tab_span != 0);
} else if (c < ' ') {
// nop
} else {
int cw = charWidth(c);
if (c < 0xffff) {
vec.push_back(c);
} else {
int a = c >> 16;
if (a > 0 && a <= 0x20) {
a--;
int b = c & 0x03ff;
a = (a << 6) | ((c >> 10) & 0x003f);
a |= 0xd800;
b |= 0xdc00;
vec.push_back(a);
vec.push_back(b);
}
}
col += cw;
}
if ((anchor_a >= 0 || anchor_b >= 0) && anchor_a != anchor_b) {
if (!flag_a && col >= anchor_a) {
Flush(line.byte_offset + offset, &next_offset);
flag_a = true;
}
if (!flag_b && col >= anchor_b) {
Flush(line.byte_offset + offset, &next_offset);
flag_b = true;
}
}
return true;
});
}
Flush(line.byte_offset + offset, &next_offset);
return res;
}
void AbstractCharacterBasedApplication::fetchCurrentLine() const
{
QByteArray line;
const int row = cx()->current_row;
int lines = documentLines();
if (row >= 0 && row < lines) {
line = cx()->engine->document.lines[row].text;
}
m->parsed_row_index = row;
m->parsed_line = line;
}
void AbstractCharacterBasedApplication::clearParsedLine()
{
m->parsed_row_index = -1;
m->parsed_for_edit = false;
m->parsed_line.clear();
}
int AbstractCharacterBasedApplication::cursorX() const
{
return cx()->current_col - cx()->scroll_col_pos;
}
int AbstractCharacterBasedApplication::cursorY() const
{
return cx()->current_row - cx()->scroll_row_pos;
}
int AbstractCharacterBasedApplication::screenWidth() const
{
return m->screen_width;
}
int AbstractCharacterBasedApplication::screenHeight() const
{
return m->screen_height;
}
void AbstractCharacterBasedApplication::setScreenSize(int w, int h, bool update_layout)
{
m->screen_width = w;
m->screen_height = h;
if (update_layout) {
layoutEditor();
}
}
bool AbstractCharacterBasedApplication::isPaintingSuppressed() const
{
return m->is_painting_suppressed;
}
void AbstractCharacterBasedApplication::setPaintingSuppressed(bool f)
{
m->is_painting_suppressed = f;
}
void AbstractCharacterBasedApplication::commitLine(std::vector<uint32_t> const &vec)
{
if (isReadOnly()) return;
QByteArray ba;
if (!vec.empty()){
utf32 u32(&vec[0], vec.size());
u32.to_utf8([&](char c, int pos){
(void)pos;
ba.push_back(c);
return true;
});
}
if (m->parsed_row_index == 0 && engine()->document.lines.empty()) {
Document::Line newline;
engine()->document.lines.push_back(newline);
}
Document::Line *line = &engine()->document.lines[m->parsed_row_index];
if (m->parsed_row_index == 0) {
line->byte_offset = 0;
line->line_number = (line->type == Document::Line::Unknown) ? 0 : 1;
}
line->text = ba;
if (m->valid_line_index > m->parsed_row_index) {
m->valid_line_index = m->parsed_row_index;
}
int y = m->parsed_row_index - cx()->scroll_row_pos + cx()->viewport_org_y;
if (y >= 0 && y < (int)m->line_flags.size()) {
m->line_flags[y] |= LineChanged;
}
}
void AbstractCharacterBasedApplication::parseLine(std::vector<uint32_t> *vec, int increase_hint, bool force)
{
if (force) {
clearParsedLine();
}
if (force || !m->parsed_for_edit) {
fetchCurrentLine();
m->parsed_col_index = internalParseLine(vec ? vec : &m->prepared_current_line, increase_hint);
m->parsed_for_edit = true;
} else {
if (vec) {
*vec = m->prepared_current_line;
}
}
}
QByteArray AbstractCharacterBasedApplication::parsedLine() const
{
return m->parsed_line;
}
bool AbstractCharacterBasedApplication::isCurrentLineWritable() const
{
if (isReadOnly()) return false;
int row = cx()->current_row;
if (row >= 0 && row < cx()->engine->document.lines.size()) {
if (cx()->engine->document.lines[row].type != Document::Line::Unknown) {
return true;
}
}
return false;
}
int AbstractCharacterBasedApplication::editorViewportWidth() const
{
return cx()->viewport_width;
}
int AbstractCharacterBasedApplication::editorViewportHeight() const
{
return cx()->viewport_height;
}
int AbstractCharacterBasedApplication::print(int x, int y, QString const &text, const AbstractCharacterBasedApplication::Option &opt)
{
CharAttr attr = opt.char_attr;
if (opt.char_attr.flags & CharAttr::Selected) {
attr.index = CharAttr::Invert;
}
if (opt.char_attr.flags & CharAttr::CurrentLine) {
attr.index = CharAttr::Hilite;
}
const int screen_w = screenWidth();
const int screen_h = screenHeight();
int x_start = 0;
int y_start = 0;
int x_limit = screen_w;
int y_limit = screen_h;
if (!opt.clip.isNull()) {
x_start = opt.clip.x();
y_start = opt.clip.y();
x_limit = x_start + opt.clip.width();
y_limit = y_start + opt.clip.height();
}
if (y >= y_start && y < y_limit) {
bool changed = false;
int x2 = x;
int y2 = y;
if (text.isEmpty()) {
changed = true; // set changed flag if text is empty
} else {
for (int i = 0; i < text.size(); i++) {
ushort c = text.utf16()[i];
if (c < ' ' || c == 0x7f) continue;
int cw = charWidth(c);
if (x2 + cw > x_limit) {
break;
}
for (int j = 0; j < cw; j++) {
if (x2 >= x_start && x2 < screen_w) {
int o = y2 * screen_w + x2;
CharAttr a;
if (j == 0) {
a = attr;
} else {
c = -1;
}
if (c != m->screen[o].c || a != m->screen[o].a) {
m->screen[o].c = c;
m->screen[o].a = a;
changed = true;
}
}
x2++;
}
}
}
if (changed) {
m->line_flags[y] |= LineChanged;
}
x = x2;
}
return x;
}
void AbstractCharacterBasedApplication::initEngine(std::shared_ptr<TextEditorContext> const &cx)
{
cx->engine = std::make_shared<TextEditorEngine>();
}
TextEditorContext *AbstractCharacterBasedApplication::cx()
{
if (dialog_cx) {
if (!dialog_cx->engine) {
initEngine(dialog_cx);
}
return dialog_cx.get();
}
if (!editor_cx->engine) {
initEngine(editor_cx);
}
return editor_cx.get();
}
const TextEditorContext *AbstractCharacterBasedApplication::cx() const
{
return const_cast<AbstractCharacterBasedApplication *>(this)->cx();
}
TextEditorEnginePtr AbstractCharacterBasedApplication::engine()
{
Q_ASSERT(cx()->engine);
return cx()->engine;
}
void AbstractCharacterBasedApplication::setTextEditorEngine(TextEditorEnginePtr const &e)
{
cx()->engine = e;
}
void AbstractCharacterBasedApplication::setDocument(QList<Document::Line> const *source)
{
document()->lines = *source;
}
void AbstractCharacterBasedApplication::openFile(QString const &path)
{
document()->lines.clear();
QFile file(path);
if (file.open(QFile::ReadOnly)) {
size_t offset = 0;
QString line;
unsigned int linenum = 0;
while (!file.atEnd()) {
linenum++;
Document::Line line(file.readLine());
line.byte_offset = offset;
line.type = Document::Line::Normal;
line.line_number = linenum;
document()->lines.push_back(line);
offset += line.text.size();
}
int n = line.size();
if (n > 0) {
ushort const *p = line.utf16();
ushort c = p[n - 1];
if (c == '\r' || c == '\n') {
document()->lines.push_back(Document::Line(QByteArray()));
}
}
m->valid_line_index = (int)document()->lines.size();
setRecentlyUsedPath(path);
}
scrollToTop();
}
void AbstractCharacterBasedApplication::saveFile(QString const &path)
{
QFile file(path);
if (file.open(QFile::WriteOnly)) {
for (Document::Line const &line : document()->lines) {
file.write(line.text);
}
}
}
void AbstractCharacterBasedApplication::pressEnter()
{
deleteIfSelected();
if (isDialogMode()) {
closeDialog(true);
} else {
writeNewLine();
}
}
void AbstractCharacterBasedApplication::pressEscape()
{
if (isTerminalMode()) {
m->escape_sequence.write(0x1b);
return;
}
if (isDialogMode()) {
closeDialog(false);
return;
}
deselect();
updateVisibility(false, false, false);
}
AbstractCharacterBasedApplication::State AbstractCharacterBasedApplication::state() const
{
return m->state;
}
Document *AbstractCharacterBasedApplication::document()
{
return &engine()->document;
}
int AbstractCharacterBasedApplication::documentLines() const
{
return const_cast<AbstractCharacterBasedApplication *>(this)->document()->lines.size();
}
bool AbstractCharacterBasedApplication::isSingleLineMode() const
{
return cx()->single_line;
}
void AbstractCharacterBasedApplication::setLineMargin(int n)
{
m->line_margin = n;
}
void AbstractCharacterBasedApplication::ensureCurrentLineVisible()
{
int margin = (cx()->viewport_height >= 6 && !isSingleLineMode()) ? m->line_margin : 0;
int pos = cx()->scroll_row_pos;
int top = cx()->current_row - margin;
int bottom = cx()->current_row + 1 - editorViewportHeight() + margin;
if (pos > top) pos = top;
if (pos < bottom) pos = bottom;
if (pos < 0) pos = 0;
if (cx()->scroll_row_pos != pos) {
cx()->scroll_row_pos = pos;
invalidateArea();
}
}
int AbstractCharacterBasedApplication::decideColumnScrollPos() const
{
int x = cx()->current_col;
int w = editorViewportWidth() - RIGHT_MARGIN;
if (w < 0) w = 0;
return x > w ? (cx()->current_col - w) : 0;
}
int AbstractCharacterBasedApplication::calcVisualWidth(const Document::Line &line) const
{
QList<FormattedLine> lines = formatLine(line, cx()->tab_span);
int x = 0;
for (FormattedLine const &line : lines) {
if (line.text.isEmpty()) continue;
ushort const *ptr = line.text.utf16();
ushort const *end = ptr + line.text.size();
while (1) {
int c = -1;
if (ptr < end) {
c = *ptr;
ptr++;
}
if (c == -1 || c == '\r' || c == '\n') {
break;
}
if (c == '\t') {
x += cx()->tab_span;
x -= x % cx()->tab_span;
} else {
x += charWidth(c);
}
}
}
return x;
}
void AbstractCharacterBasedApplication::clearRect(int x, int y, int w, int h)
{
int scr_w = screenWidth();
int scr_h = screenHeight();
int y0 = y;
int y1 = y + h;
if (y0 < 0) y0 = 0;
if (y1 > scr_h) y1 = scr_h;
int x0 = x;
int x1 = x + w;
if (x0 < 0) x0 = 0;
if (x1 > scr_w) x1 = scr_w;
for (int y = y0; y < y1; y++) {
for (int x = x0; x < x1; x++) {
int o = y * scr_w + x;
m->screen[o] = Character();
}
}
}
int AbstractCharacterBasedApplication::calcIndexToColumn(const std::vector<uint32_t> &vec, int index) const
{
int col = 0;
for (int i = 0; i < index; i++) {
uint32_t c = vec.at(i);
if (c == '\t') {
col += cx()->tab_span;
col -= col % cx()->tab_span;
} else {
col += charWidth(c);
}
}
return col;
}
void AbstractCharacterBasedApplication::savePos()
{
TextEditorContext *p = editor_cx.get();
if (p) {
p->saved_row = p->current_row;
p->saved_col = p->current_col;
p->saved_col_hint = p->current_col_hint;
}
}
void AbstractCharacterBasedApplication::restorePos()
{
TextEditorContext *p = editor_cx.get();
if (p) {
p->current_row = p->saved_row;
p->current_col = p->saved_col;
p->current_col_hint = p->saved_col_hint;
}
}
void AbstractCharacterBasedApplication::setCursorRow(int row, bool auto_scroll, bool by_mouse)
{
if (cx()->current_row == row) return;
if (isShiftModifierPressed()) {
if (selection_anchor_0.enabled == SelectionAnchor::No) {
setSelectionAnchor(SelectionAnchor::EnabledEasy, true, auto_scroll);
selection_anchor_1 = selection_anchor_0;
}
} else if (selection_anchor_0.enabled == SelectionAnchor::EnabledEasy) {
setSelectionAnchor(SelectionAnchor::No, false, auto_scroll);
}
cx()->current_row = row;
if (selection_anchor_0.enabled != SelectionAnchor::No) {
setSelectionAnchor(selection_anchor_0.enabled, true, auto_scroll);
}
m->cursor_moved_by_mouse = by_mouse;
}
void AbstractCharacterBasedApplication::setCursorCol(int col, bool auto_scroll, bool by_mouse)
{
if (cx()->current_col == col) {
cx()->current_col_hint = col;
return;
}
if (isShiftModifierPressed()) {
if (selection_anchor_0.enabled == SelectionAnchor::No) {
setSelectionAnchor(SelectionAnchor::EnabledEasy, true, auto_scroll);
selection_anchor_1 = selection_anchor_0;
}
} else if (selection_anchor_0.enabled == SelectionAnchor::EnabledEasy) {
setSelectionAnchor(SelectionAnchor::No, false, auto_scroll);
}
cx()->current_col = col;
cx()->current_col_hint = col;
if (selection_anchor_0.enabled != SelectionAnchor::No) {
setSelectionAnchor(selection_anchor_0.enabled, true, auto_scroll);
}
m->cursor_moved_by_mouse = by_mouse;
}
void AbstractCharacterBasedApplication::setCursorPos(int row, int col)
{
setCursorRow(row, false);
setCursorCol(col, false);
}
void AbstractCharacterBasedApplication::setCursorColByIndex(std::vector<uint32_t> const &vec, int col_index)
{
int col = calcIndexToColumn(vec, col_index);
setCursorCol(col);
}
int AbstractCharacterBasedApplication::nextTabStop(int x) const
{
x += cx()->tab_span;
x -= x % cx()->tab_span;
return x;
}
void AbstractCharacterBasedApplication::editSelected(EditOperation op, std::vector<uint32_t> *cutbuffer)
{
if (isReadOnly() && op == EditOperation::Cut) {
op = EditOperation::Copy;
}
SelectionAnchor a = selection_anchor_0;
SelectionAnchor b = selection_anchor_1;
if (!a.enabled) return;
if (!b.enabled) return;
if (a == b) return;
auto UpdateVisibility = [&](){
updateVisibility(false, false, false);
};
if (cutbuffer) {
cutbuffer->clear();
}
std::list<std::vector<uint32_t>> cutlist;
if (a.row > b.row) {
std::swap(a, b);
} else if (a.row == b.row) {
if (a.col > b.col) {
std::swap(a, b);
}
}
int curr_row = cx()->current_row;
int curr_col = cx()->current_col;
cx()->current_row = b.row;
cx()->current_col = b.col;
if (a.row == b.row) {
std::vector<uint32_t> vec1;
parseLine(&vec1, 0, true);
auto begin = vec1.begin() + calcColumnToIndex(a.col);
auto end = vec1.begin() + calcColumnToIndex(b.col);
if (cutbuffer) {
std::vector<uint32_t> cut;
cut.insert(cut.end(), begin, end);
cutlist.push_back(std::move(cut));
}
if (op == EditOperation::Cut) {
vec1.erase(begin, end);
commitLine(vec1);
UpdateVisibility();
}
} else {
std::vector<uint32_t> vec1;
parseLine(&vec1, 0, true);
{
auto begin = vec1.begin();
auto end = vec1.begin() + calcColumnToIndex(b.col);
if (cutbuffer) {
std::vector<uint32_t> cut;
cut.insert(cut.end(), begin, end);
cutlist.push_back(std::move(cut));
}
if (op == EditOperation::Cut) {
vec1.erase(begin, end);
commitLine(vec1);
UpdateVisibility();
}
}
int n = b.row - a.row;
for (int i = 0; i < n; i++) {
QList<Document::Line> *lines = &cx()->engine->document.lines;
if (cutbuffer && i > 0) {
cx()->current_row = b.row - i;
cx()->current_col = 0;
std::vector<uint32_t> cut;
parseLine(&cut, 0, true);
cutlist.push_back(std::move(cut));
}
if (op == EditOperation::Cut) {
lines->erase(lines->begin() + b.row - i);
}
}
cx()->current_row = a.row;
cx()->current_col = a.col;
int index = calcColumnToIndex(a.col);
std::vector<uint32_t> vec2;
parseLine(&vec2, 0, true);
if (cutbuffer) {
std::vector<uint32_t> cut;
cut.insert(cut.end(), vec2.begin() + index, vec2.end());
cutlist.push_back(std::move(cut));
}
if (op == EditOperation::Cut) {
vec2.resize(index);
vec2.insert(vec2.end(), vec1.begin(), vec1.end());
commitLine(vec2);
UpdateVisibility();
}
}
if (cutbuffer) {
size_t size = 0;
for (std::vector<uint32_t> const &v : cutlist) {
size += v.size();
}
cutbuffer->reserve(size);
for (auto it = cutlist.rbegin(); it != cutlist.rend(); it++) {
std::vector<uint32_t> const &v = *it;
cutbuffer->insert(cutbuffer->end(), v.begin(), v.end());
}
}
if (op == EditOperation::Cut) {
deselect();
setCursorPos(a.row, a.col);
invalidateArea(a.row - cx()->scroll_row_pos);
} else {
cx()->current_row = curr_row;
cx()->current_col = curr_col;
invalidateArea(curr_row - cx()->scroll_row_pos);
}
clearParsedLine();
UpdateVisibility();
}
void AbstractCharacterBasedApplication::edit_(EditOperation op)
{
std::vector<uint32_t> u32buf;
editSelected(op, &u32buf);
if (u32buf.empty()) return;
std::vector<ushort> u16buf;
u16buf.reserve(1024);
utf32(&u32buf[0], u32buf.size()).to_utf16([&](uint16_t c){
u16buf.push_back(c);
return true;
});
if (!u16buf.empty()) {
QString s = QString::fromUtf16(&u16buf[0], u16buf.size());
qApp->clipboard()->setText(s);
}
}
bool AbstractCharacterBasedApplication::deleteIfSelected()
{
if (selection_anchor_0.enabled && selection_anchor_1.enabled) {
if (selection_anchor_0 != selection_anchor_1) {
editSelected(EditOperation::Cut, nullptr);
return true;
}
}
return false;
}
void AbstractCharacterBasedApplication::doDelete()
{
if (isReadOnly()) return;
if (isTerminalMode()) return;
if (deleteIfSelected()) {
return;
}
parseLine(nullptr, 0, false);
std::vector<uint32_t> *vec = &m->prepared_current_line;
int index = m->parsed_col_index;
int c = -1;
if (index >= 0 && index < (int)vec->size()) {
c = (*vec)[index];
}
if (c == '\n' || c == '\r' || c == -1) {
if (index == 0) {
m->parsed_row_index--;
}
invalidateAreaBelowTheCurrentLine();
if (isSingleLineMode()) {
// nop
} else {
if (c != -1) {
vec->erase(vec->begin() + index);
if (c == '\r' && index < (int)vec->size() && (*vec)[index] == '\n') {
vec->erase(vec->begin() + index);
}
}
if (vec->empty()) {
clearParsedLine();
if (cx()->current_row + 1 < (int)cx()->engine->document.lines.size()) {
cx()->engine->document.lines.erase(cx()->engine->document.lines.begin() + cx()->current_row);
}
} else {
commitLine(*vec);
setCursorColByIndex(*vec, index);
if (index == (int)vec->size()) {
int nextrow = cx()->current_row + 1;
int lines = documentLines();
if (nextrow < lines) {
Document::Line *ba1 = &cx()->engine->document.lines[cx()->current_row];
Document::Line const &ba2 = cx()->engine->document.lines[nextrow];
ba1->text.append(ba2.text);
cx()->engine->document.lines.erase(cx()->engine->document.lines.begin() + nextrow);
}
}
}
clearParsedLine();
updateVisibility(true, true, true);
}
} else {
vec->erase(vec->begin() + index);
commitLine(*vec);
setCursorColByIndex(*vec, index);
updateVisibility(true, true, true);
}
}
void AbstractCharacterBasedApplication::doBackspace()
{
if (isReadOnly()) return;
if (isTerminalMode()) return;
if (deleteIfSelected()) {
return ;
}
if (cx()->current_row > 0 || cx()->current_col > 0) {
setPaintingSuppressed(true);
moveCursorLeft();
doDelete();
setPaintingSuppressed(false);
updateVisibility(true, true, true);
}
}
bool AbstractCharacterBasedApplication::isDialogMode()
{
return m->dialog_mode;
}
void AbstractCharacterBasedApplication::setDialogOption(QString const &title, QString const &value, DialogHandler handler)
{
m->dialog_title = title;
m->dialog_value = value;
m->dialog_handler = handler;
}
void AbstractCharacterBasedApplication::setDialogMode(bool f)
{
if (f) {
if (!dialog_cx) {
int y = screenHeight() - 2;
dialog_cx = std::make_shared<TextEditorContext>();
dialog_cx->engine = std::make_shared<TextEditorEngine>();
dialog_cx->single_line = true;
dialog_cx->viewport_org_x = 0;
dialog_cx->viewport_org_y = y + 1;
dialog_cx->viewport_width = screenWidth();
dialog_cx->viewport_height = 1;
}
dialog_cx->engine->document.lines.push_back(Document::Line(m->dialog_value.toUtf8()));
editor_cx->viewport_height = screenHeight() - m->header_line - 2;
m->dialog_mode = true;
clearParsedLine();
moveCursorEnd();
} else {
dialog_cx.reset();
m->dialog_mode = false;
layoutEditor();
clearParsedLine();
updateVisibility(true, true, true);
}
}
void AbstractCharacterBasedApplication::execDialog(QString const &dialog_title, QString dialog_value, DialogHandler handler)
{
setDialogOption(dialog_title, dialog_value, handler);
setDialogMode(true);
}
void AbstractCharacterBasedApplication::closeDialog(bool result)
{
if (isDialogMode()) {
deselect();
QString line;
if (!dialog_cx->engine->document.lines.empty()) {
Document::Line const &l = dialog_cx->engine->document.lines.front();
line = QString::fromUtf8(l.text);
}
setDialogMode(false);
if (m->dialog_handler) {
m->dialog_handler(result, line);
}
return;
}
}
int AbstractCharacterBasedApplication::internalParseLine(std::vector<uint32_t> *vec, int increase_hint) const
{
vec->clear();
int index = -1;
int col = 0;
int len = m->parsed_line.size();
if (len > 0) {
vec->reserve(len + increase_hint);
char const *src = m->parsed_line.data();
utf8 u8(src, len);
while (1) {
int n = 0;
uint32_t c = u8.next();
if (c == 0) {
n = 1;
} else {
if (c == '\t') {
int z = nextTabStop(col);
n = z - col;
} else {
n = charWidth(c);
}
}
if (col <= cx()->current_col && col + n > cx()->current_col) {
index = (int)vec->size();
}
if (c == 0) break;
col += n;
vec->push_back(c);
}
}
return index;
}
int AbstractCharacterBasedApplication::calcColumnToIndex(int column)
{
int index = 0;
if (column > 0) {
fetchCurrentLine();
int col = 0;
int len = m->parsed_line.size();
if (len > 0) {
char const *src = m->parsed_line.data();
utf8 u8(src, len);
while (1) {
uint32_t c = u8.next();
int n = 0;
if (c == '\r' || c == '\n' || c == 0) {
break;
}
if (c == '\t') {
int z = nextTabStop(col);
n = z - col;
} else {
n = charWidth(c);
}
col += n;
index++;
if (col >= column) {
break;
}
}
}
}
return index;
}
void AbstractCharacterBasedApplication::invalidateArea(int top_y)
{
int y0 = cx()->viewport_org_y;
int y1 = cx()->viewport_height + y0;
top_y += y0;
if (y0 < top_y) y0 = top_y;
int n = m->line_flags.size();
if (y0 < 0) y0 = 0;
if (y1 > n) y1 = n;
for (int y = y0; y < y1; y++) {
m->line_flags[y] |= LineChanged;
}
}
void AbstractCharacterBasedApplication::invalidateAreaBelowTheCurrentLine()
{
int y;
y = m->parsed_row_index;
if (m->valid_line_index > y) {
m->valid_line_index = y;
}
y = m->parsed_row_index - cx()->scroll_row_pos;
invalidateArea(y);
}
int AbstractCharacterBasedApplication::scrollBottomLimit() const
{
return documentLines() - editorViewportHeight() / 2;
}
void AbstractCharacterBasedApplication::writeCR()
{
deleteIfSelected();
setCursorCol(0);
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::moveCursorOut()
{
setCursorRow(-1);
}
void AbstractCharacterBasedApplication::moveCursorHome()
{
fetchCurrentLine();
QString line = m->parsed_line;
ushort const *ptr = line.utf16();
ushort const *end = ptr + line.size();
int x = 0;
while (1) {
int c = -1;
if (ptr < end) {
c = *ptr;
ptr++;
}
if (c == ' ') {
x++;
} else if (c == '\t') {
x = nextTabStop(x);
} else {
break;
}
}
if (x == cx()->current_col) {
x = 0;
}
setCursorCol(x);
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::moveCursorEnd()
{
fetchCurrentLine();
QByteArray line = m->parsed_line;
int col = calcVisualWidth(Document::Line(line));
setCursorCol(col);
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::scrollUp()
{
if (cx()->scroll_row_pos > 0) {
cx()->scroll_row_pos--;
invalidateArea();
clearParsedLine();
updateVisibility(false, false, true);
}
}
void AbstractCharacterBasedApplication::scrollDown()
{
int limit = scrollBottomLimit();
if (cx()->scroll_row_pos < limit) {
cx()->scroll_row_pos++;
invalidateArea();
clearParsedLine();
updateVisibility(false, false, true);
}
}
void AbstractCharacterBasedApplication::moveCursorUp()
{
if (isSingleLineMode()) {
// nop
} else if (cx()->current_row > 0) {
setCursorRow(cx()->current_row - 1);
clearParsedLine();
updateVisibility(true, false, true);
}
}
void AbstractCharacterBasedApplication::moveCursorDown()
{
if (isSingleLineMode()) {
// nop
} else if (cx()->current_row + 1 < (int)document()->lines.size()) {
setCursorRow(cx()->current_row + 1);
clearParsedLine();
updateVisibility(true, false, true);
}
}
void AbstractCharacterBasedApplication::scrollToTop()
{
if (isSingleLineMode()) return;
setCursorRow(0);
setCursorCol(0);
cx()->scroll_row_pos = 0;
invalidateArea();
clearParsedLine();
updateVisibility(true, false, true);
}
void AbstractCharacterBasedApplication::moveCursorLeft()
{
if (!isShiftModifierPressed() && selection_anchor_0.enabled && selection_anchor_1.enabled) { // 選択領域があったら
if (selection_anchor_0 != selection_anchor_1) {
SelectionAnchor a = std::min(selection_anchor_0, selection_anchor_1);
deselect();
setCursorRow(a.row);
setCursorCol(a.col);
updateVisibility(true, true, true);
return;
}
}
if (cx()->current_col == 0) {
if (isSingleLineMode()) {
// nop
} else {
if (cx()->current_row > 0) {
setCursorRow(cx()->current_row - 1);
clearParsedLine();
moveCursorEnd();
}
}
return;
}
int col = 0;
int newcol = 0;
int index;
for (index = 0; index < (int)m->prepared_current_line.size(); index++) {
ushort c = m->prepared_current_line[index];
if (c == '\r' || c == '\n') {
break;
}
newcol = col;
if (c == '\t') {
col = nextTabStop(col);
} else {
col += charWidth(c);
}
if (col >= cx()->current_col) {
break;
}
}
m->parsed_col_index = index;
setCursorCol(newcol);
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::moveCursorRight()
{
if (!isShiftModifierPressed() && selection_anchor_0.enabled && selection_anchor_1.enabled) { // 選択領域があったら
if (selection_anchor_0 != selection_anchor_1) {
SelectionAnchor a = std::max(selection_anchor_0, selection_anchor_1);
deselect();
setCursorRow(a.row);
setCursorCol(a.col);
updateVisibility(true, true, true);
return;
}
}
int col = 0;
int i = 0;
while (1) {
int c = -1;
if (i < (int)m->prepared_current_line.size()) {
c = m->prepared_current_line[i];
}
if (c == '\r' || c == '\n' || c == -1) {
if (!isSingleLineMode()) {
int nextrow = cx()->current_row + 1;
int lines = document()->lines.size();
if (nextrow < lines) {
setCursorPos(nextrow, 0);
clearParsedLine();
updateVisibility(true, true, true);
return;
}
}
break;
}
if (c == '\t') {
col = nextTabStop(col);
} else {
col += charWidth(c);
}
if (col > cx()->current_col) {
break;
}
i++;
}
if (col != cx()->current_col) {
setCursorCol(col);
clearParsedLine();
updateVisibility(true, true, true);
}
}
void AbstractCharacterBasedApplication::movePageUp()
{
if (!isSingleLineMode()) {
int step = editorViewportHeight();
setCursorRow(cx()->current_row - step);
cx()->scroll_row_pos -= step;
if (cx()->current_row < 0) {
cx()->current_row = 0;
}
if (cx()->scroll_row_pos < 0) {
cx()->scroll_row_pos = 0;
}
invalidateArea();
clearParsedLine();
updateVisibility(true, false, true);
}
}
void AbstractCharacterBasedApplication::movePageDown()
{
if (!isSingleLineMode()) {
int limit = documentLines();
if (limit > 0) {
limit--;
int step = editorViewportHeight();
setCursorRow(cx()->current_row + step);
cx()->scroll_row_pos += step;
if (cx()->current_row > limit) {
cx()->current_row = limit;
}
limit = scrollBottomLimit();
if (cx()->scroll_row_pos > limit) {
cx()->scroll_row_pos = limit;
}
} else {
setCursorRow(0);
cx()->scroll_row_pos = 0;
}
invalidateArea();
clearParsedLine();
updateVisibility(true, false, true);
}
}
void AbstractCharacterBasedApplication::scrollRight()
{
if (cx()->scroll_col_pos > 0) {
cx()->scroll_col_pos--;
} else {
cx()->scroll_col_pos = 0;
}
invalidateArea();
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::scrollLeft()
{
cx()->scroll_col_pos++;
invalidateArea();
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::addNewLineToBottom()
{
int row = cx()->engine->document.lines.size();
if (cx()->current_row >= row) {
setCursorPos(row, 0);
cx()->engine->document.lines.push_back(Document::Line(QByteArray()));
}
}
void AbstractCharacterBasedApplication::appendNewLine(std::vector<uint32_t> *vec)
{
if (isSingleLineMode()) return;
vec->push_back('\r');
vec->push_back('\n');
}
void AbstractCharacterBasedApplication::writeNewLine()
{
if (isReadOnly()) return;
if (isSingleLineMode()) return;
if (!isCurrentLineWritable()) return;
invalidateAreaBelowTheCurrentLine();
std::vector<uint32_t> curr_line;
parseLine(&curr_line, 0, false);
int index = m->parsed_col_index;
if (index < 0) {
addNewLineToBottom();
index = 0;
}
std::vector<uint32_t> next_line;
// split line
next_line.insert(next_line.end(), curr_line.begin() + index, curr_line.end());
// shrink current line
curr_line.resize(index);
// append new line code
appendNewLine(&curr_line);
// next line index
cx()->current_row = m->parsed_row_index + 1;
// commit current line
commitLine(curr_line);
// insert next line
m->parsed_row_index = cx()->current_row;
engine()->document.lines.insert(engine()->document.lines.begin() + m->parsed_row_index, Document::Line(QByteArray()));
// commit next line
commitLine(next_line);
setCursorColByIndex(next_line, 0);
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::makeColumnPosList(std::vector<int> *out)
{
out->clear();
std::vector<uint32_t> const *line = &m->prepared_current_line;
int x = 0;
while (1) {
size_t index = out->size();
out->push_back(x);
int n;
int c = -1;
if (index < line->size()) {
c = line->at(index);
}
if (c == '\r' || c == '\n' || c == -1) {
break;
}
if (c == '\t') {
int z = nextTabStop(x);
n = z - x;
} else {
n = charWidth(c);
}
x += n;
}
}
void AbstractCharacterBasedApplication::updateCursorPos(bool auto_scroll)
{
if (!cx()->engine) {
return;
}
parseLine(&m->prepared_current_line, 0, false);
int index = 0;
int char_span = 0;
int col = cx()->current_col_hint;
{
std::vector<int> pts;
makeColumnPosList(&pts);
if (pts.size() > 1) {
int newcol = pts.back();
int newindex = pts.size() - 1;
for (size_t i = 0; i + 1 < pts.size(); i++) {
int x = pts[i];
if (x <= col && col < pts[i + 1]) {
char_span = pts[i + 1] - pts[i];
newindex = i;
newcol = x;
break;
}
}
index = newindex;
col = newcol;
} else if (!pts.empty()) {
col = pts.back();
} else {
col = 0;
}
}
m->parsed_col_index = index;
cx()->current_col = col;
if (char_span < 1) {
char_span = 1;
}
cx()->current_char_span = char_span;
if (auto_scroll) {
int pos = decideColumnScrollPos();
if (cx()->scroll_col_pos != pos) {
cx()->scroll_col_pos = pos;
invalidateArea();
}
}
}
void AbstractCharacterBasedApplication::printInvertedBar(int x, int y, char const *text, int padchar)
{
int w = screenWidth();
int o = w * y;
for (int i = 0; i < w; i++) {
m->screen[o + i].c = 0;
}
AbstractCharacterBasedApplication::Option opt;
opt.char_attr = CharAttr::Invert;
print(x, y, text, opt);
for (int i = 0; i < w; i++) {
if (m->screen[o + i].c == 0) {
m->screen[o + i].c = padchar;
}
m->screen[o + i].a = opt.char_attr;
}
}
QString AbstractCharacterBasedApplication::statusLine() const
{
QString text = "[%1:%2]";
text = text.arg(cx()->current_row + 1).arg(cx()->current_col + 1);
return text;
}
int AbstractCharacterBasedApplication::printArea(TextEditorContext const *cx, const SelectionAnchor *sel_a, const SelectionAnchor *sel_b)
{
int end_of_line_y = -1;
if (cx) {
int height = cx->viewport_height;
QRect clip(cx->viewport_org_x, cx->viewport_org_y, cx->viewport_width, height);
int row = cx->scroll_row_pos;
for (int i = 0; i < height; i++) {
if (row < 0) continue;
int y = cx->viewport_org_y + i;
if (row < (int)cx->engine->document.lines.size()) {
if (i < height) {
int x = cx->viewport_org_x - cx->scroll_col_pos;
Document::Line const &line = cx->engine->document.lines[row];
int anchor_a = -1;
int anchor_b = -1;
if (sel_a && sel_a->enabled && sel_b && sel_b->enabled) {
SelectionAnchor a = *sel_a;
SelectionAnchor b = *sel_b;
if (a.row > b.row) {
std::swap(a, b);
} else if (a.row == b.row) {
if (a.col > b.col) {
std::swap(a, b);
}
}
if (row > a.row && row < b.row) {
anchor_a = 0;
} else {
if (row == a.row) {
anchor_a = a.col;
}
if (row == b.row) {
anchor_b = b.col;
}
}
}
QList<FormattedLine> lines = formatLine(line, cx->tab_span, anchor_a, anchor_b);
for (FormattedLine const &line : lines) {
AbstractCharacterBasedApplication::Option opt;
if (line.atts & FormattedLine::StyleID) {
opt.char_attr.color = QColor(line.atts & 0xff, (line.atts >> 8) & 0xff, (line.atts >> 16) & 0xff);
}
opt.clip = clip;
if (line.isSelected()) {
opt.char_attr.flags |= CharAttr::Selected;
}
x = print(x, y, line.text, opt);
}
int end_x = clip.x() + clip.width();
if (x < end_x) {
if (x < clip.left()) {
x = clip.left();
}
if (x < end_x) {
clearRect(x, y, end_x - x, 1);
}
}
}
} else {
if (end_of_line_y < 0) {
end_of_line_y = i;
}
clearRect(cx->viewport_org_x, y, cx->viewport_width, 1);
if (y >= 0 && y < (int)m->line_flags.size()) {
m->line_flags[y] |= LineChanged;
}
}
row++;
}
}
return end_of_line_y;
}
void AbstractCharacterBasedApplication::paintLineNumbers(std::function<void(int, QString, Document::Line const *line)> const &draw)
{
auto Line = [&](int index)->Document::Line &{
return editor_cx->engine->document.lines[index];
};
int rightpadding = 2;
int left_margin = editor_cx->viewport_org_x;
int num = 1;
size_t offset = 0;
for (int i = 0; i < editor_cx->viewport_height; i++) {
char tmp[100];
Q_ASSERT(left_margin < (int)sizeof(tmp));
memset(tmp, ' ', left_margin);
tmp[left_margin] = 0;
int row = editor_cx->scroll_row_pos + i;
Document::Line *line = nullptr;
if (row < (int)editor_cx->engine->document.lines.size()) {
if (m->valid_line_index < 0) {
m->valid_line_index = 0;
Document::Line *p = &Line(0);
if (p->type != Document::Line::Unknown) {
p->byte_offset = offset;
p->line_number = num;
offset += p->text.size();
num++;
}
}
line = &Line(row);
if (row >= m->valid_line_index) {
{
Document::Line const &line = Line(m->valid_line_index);
offset = line.byte_offset;
num = line.line_number;
}
while (m->valid_line_index <= row) {
Document::Line const &line = Line(m->valid_line_index);
if (line.type != Document::Line::Unknown) {
offset += line.text.size();
num++;
}
m->valid_line_index++;
if (m->valid_line_index < editor_cx->engine->document.lines.size()) {
Document::Line *p = &Line(m->valid_line_index);
p->byte_offset = offset;
p->line_number = num;
}
}
}
if (left_margin > 1) {
unsigned int linenum = -1;
if (row < m->valid_line_index) {
#if 1
linenum = line->line_number;
#else
linenum = line->byte_offset;
#endif
}
if (linenum != (unsigned int)-1 && line->type != Document::Line::Unknown) {
sprintf(tmp, "%*u ", left_margin - rightpadding, linenum);
}
}
}
int y = editor_cx->viewport_org_y + i;
draw(y, tmp, line);
}
}
bool AbstractCharacterBasedApplication::isAutoLayout() const
{
return m->auto_layout;
}
void AbstractCharacterBasedApplication::preparePaintScreen()
{
if (m->header_line > 0) {
char const *line = "Hello, world\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86";
printInvertedBar(0, 0, line, ' ');
}
if (m->show_line_number) {
Option opt_normal;
paintLineNumbers([&](int y, QString text, Document::Line const *line){
(void)line;
print(0, y, text + '|', opt_normal);
});
}
SelectionAnchor anchor_a;
SelectionAnchor anchor_b;
auto MakeSelectionAnchor = [&](){
if (selection_anchor_0.enabled != SelectionAnchor::No) {
anchor_a = selection_anchor_0;
#if 0
anchor_b.row = cx()->current_row;
anchor_b.col = cx()->current_col;
anchor_b.enabled = selection_anchor_0.enabled;
#else
anchor_b = selection_anchor_1;
#endif
}
};
if (isDialogMode()) {
printArea(editor_cx.get(), &anchor_a, &anchor_b);
std::string text = m->dialog_title.toStdString();
text = ' ' + text + ' ';
int y = screenHeight() - 2;
printInvertedBar(3, y, text.c_str(), '-');
MakeSelectionAnchor();
printArea(dialog_cx.get(), &anchor_a, &anchor_b);
} else {
MakeSelectionAnchor();
editor_cx->bottom_line_y = printArea(editor_cx.get(), &anchor_a, &anchor_b);
if (m->footer_line > 0) {
QString line = statusLine();
int y = screenHeight() - 1;
printInvertedBar(0, y, line.toStdString().c_str(), ' ');
}
}
}
void AbstractCharacterBasedApplication::onQuit()
{
if (!m->is_quit_enabled) return;
if (!isDialogMode()) {
m->state = State::Exit;
}
}
void AbstractCharacterBasedApplication::onOpenFile()
{
if (isReadOnly()) return;
if (!m->is_open_enabled) return;
if (!isDialogMode()) {
execDialog("Open File", recentlyUsedPath(), [&](bool ok, QString const &text){
if (ok) {
openFile(text);
}
});
}
}
void AbstractCharacterBasedApplication::onSaveFile()
{
if (!m->is_save_enabled) return;
if (!isDialogMode()) {
execDialog("Save File", recentlyUsedPath(), [&](bool ok, QString const &text){
if (ok) {
saveFile(text);
}
});
}
}
void AbstractCharacterBasedApplication::setNormalTextEditorMode(bool f)
{
m->is_quit_enabled = f;
m->is_open_enabled = f;
m->is_save_enabled = f;
setTerminalMode(!f);
}
SelectionAnchor AbstractCharacterBasedApplication::currentAnchor(SelectionAnchor::Enabled enabled)
{
SelectionAnchor a;
a.row = cx()->current_row;
a.col = cx()->current_col;
a.enabled = enabled;
return a;
}
void AbstractCharacterBasedApplication::deselect()
{
selection_anchor_0.enabled = SelectionAnchor::No;
selection_anchor_1.enabled = SelectionAnchor::No;
}
bool AbstractCharacterBasedApplication::isSelectionAnchorEnabled() const
{
return selection_anchor_0.enabled != SelectionAnchor::No;
}
void AbstractCharacterBasedApplication::setToggleSelectionAnchorEnabled(bool f)
{
m->is_toggle_selection_anchor_enabled = f;
}
void AbstractCharacterBasedApplication::setReadOnly(bool f)
{
m->is_read_only = f;
}
bool AbstractCharacterBasedApplication::isReadOnly() const
{
return m->is_read_only && !m->is_terminal_mode;
}
void AbstractCharacterBasedApplication::setSelectionAnchor(SelectionAnchor::Enabled enabled, bool update_anchor, bool auto_scroll)
{
if (update_anchor) {
selection_anchor_0 = currentAnchor(enabled);
} else {
selection_anchor_0.enabled = enabled;
}
clearParsedLine();
updateVisibility(false, false, auto_scroll);
}
void AbstractCharacterBasedApplication::toggleSelectionAnchor()
{
if (!m->is_toggle_selection_anchor_enabled) return;
setSelectionAnchor(isSelectionAnchorEnabled() ? SelectionAnchor::No : SelectionAnchor::EnabledHard, true, true);
selection_anchor_1 = selection_anchor_0;
}
void AbstractCharacterBasedApplication::editPaste()
{
if (isReadOnly()) return;
if (isTerminalMode()) return;
setPaintingSuppressed(true);
QString str = qApp->clipboard()->text();
utf16(str.utf16(), str.size()).to_utf32([&](uint32_t c){
write(c, false);
return true;
});
setPaintingSuppressed(false);
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::editCopy()
{
edit_(EditOperation::Copy);
}
void AbstractCharacterBasedApplication::editCut()
{
if (isReadOnly()) return;
if (isTerminalMode()) return;
edit_(EditOperation::Cut);
}
void AbstractCharacterBasedApplication::setWriteMode(WriteMode wm)
{
m->write_mode = wm;
}
bool AbstractCharacterBasedApplication::isInsertMode() const
{
return m->write_mode == WriteMode::Insert && !isTerminalMode();
}
bool AbstractCharacterBasedApplication::isOverwriteMode() const
{
return m->write_mode == WriteMode::Overwrite || isTerminalMode();
}
void AbstractCharacterBasedApplication::setTerminalMode(bool f)
{
m->is_terminal_mode = f;
if (isTerminalMode()) {
showHeader(false);
showFooter(false);
showLineNumber(false, 0);
setLineMargin(0);
setWriteMode(WriteMode::Overwrite);
setReadOnly(true);
}
layoutEditor();
}
bool AbstractCharacterBasedApplication::isTerminalMode() const
{
return m->is_terminal_mode;
}
bool AbstractCharacterBasedApplication::isBottom() const
{
if (cx()->current_row == m->parsed_row_index) {
if (m->parsed_col_index == (int)m->prepared_current_line.size()) {
return true;
}
}
return false;
}
void AbstractCharacterBasedApplication::moveToTop()
{
if (isSingleLineMode()) return;
deselect();
cx()->current_row = 0;
cx()->current_col = 0;
cx()->current_col_hint = 0;
cx()->scroll_row_pos = 0;
scrollToTop();
invalidateArea();
clearParsedLine();
updateVisibility(true, false, true);
}
void AbstractCharacterBasedApplication::logicalMoveToBottom()
{
deselect();
cx()->current_row = documentLines();
cx()->current_col = 0;
if (cx()->current_row > 0) {
cx()->current_row = cx()->current_row - 1;
clearParsedLine();
fetchCurrentLine();
int col = calcVisualWidth(Document::Line(m->parsed_line));
cx()->current_col = col;
cx()->current_col_hint = col;
}
cx()->scroll_row_pos = scrollBottomLimit();
}
void AbstractCharacterBasedApplication::moveToBottom()
{
if (isSingleLineMode()) return;
logicalMoveToBottom();
invalidateArea();
clearParsedLine();
updateVisibility(true, false, true);
}
int AbstractCharacterBasedApplication::findSyntax(QList<Document::CharAttr_> const *list, size_t offset)
{
int lo = 0;
int hi = list->size();
while (lo + 1 < hi) {
int mid = (lo + hi) / 2;
Document::CharAttr_ const *a = &list->at(mid);
if (offset == a->offset) {
return mid;
}
if (offset < a->offset) {
hi = mid;
} else {
lo = mid;
}
}
return lo;
}
void AbstractCharacterBasedApplication::insertSyntax(QList<Document::CharAttr_> *list, size_t offset, Document::CharAttr_ const &a)
{
int i = findSyntax(list, offset);
int n = list->size();
if (i < n) {
if (list->at(i).offset == offset) {
(*list)[i] = a;
} else {
if (i < n) {
if (a.offset < list->at(i).offset) {
if (list->at(i).color == a.color) {
(*list)[i].offset = offset;
return;
}
}
}
if (list->at(i).color == a.color) {
return;
}
if (list->at(i).offset < a.offset) {
i++;
}
list->insert(list->begin() + i, a);
while (++i < n) {
(*list)[i].offset++;
}
}
} else {
list->push_back(a);
}
}
void AbstractCharacterBasedApplication::internalWrite(const ushort *begin, const ushort *end)
{
deleteIfSelected();
clearShiftModifier();
if (cx()->engine->document.lines.isEmpty()) {
Document::Line line;
line.type = Document::Line::Normal;
line.line_number = 1;
cx()->engine->document.lines.push_back(line);
}
if (!isCurrentLineWritable()) return;
int len = end - begin;
parseLine(nullptr, len, false);
int index = m->parsed_col_index;
if (index < 0) {
addNewLineToBottom();
index = 0;
}
std::vector<uint32_t> *vec = &m->prepared_current_line;
auto WriteChar = [&](ushort c){
if (isInsertMode()) {
vec->insert(vec->begin() + index, c);
} else if (isOverwriteMode()) {
if (index < (int)vec->size()) {
ushort d = vec->at(index);
if (d == '\n' || d == '\r') {
vec->insert(vec->begin() + index, c);
} else {
vec->at(index) = c;
}
} else {
vec->push_back(c);
}
}
Document::CharAttr_ a;
a.offset = index;
a.color = m->escape_sequence.fg_color_code();
insertSyntax(&m->syntax_table, index, a);
};
ushort const *ptr = begin;
while (ptr < end) {
ushort c = *ptr;
ptr++;
if (c >= 0xd800 && c < 0xdc00) {
if (ptr < end) {
ushort d = *ptr;
if (d >= 0xdc00 && d < 0xe000) {
ptr++;
int u = 0x10000 + (c - 0xd800) * 0x400 + (d - 0xdc00);
WriteChar(u);
index++;
}
}
} else {
WriteChar(c);
index++;
}
}
m->parsed_col_index = index;
commitLine(*vec);
setCursorColByIndex(*vec, index);
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::pressLetterWithControl(int c)
{
if (c < 0 || c > 0x7f) {
return;
}
if (c < 0x40) {
c += 0x40;
}
c = toupper(c);
switch (c) {
case 'A':
toggleSelectionAnchor();
break;
case 'Q':
onQuit();
break;
case 'O':
onOpenFile();
break;
case 'S':
onSaveFile();
break;
case 'X':
editCut();
break;
case 'C':
editCopy();
break;
case 'V':
editPaste();
break;
}
}
void AbstractCharacterBasedApplication::write(uint32_t c, bool by_keyboard)
{
if (isTerminalMode()) {
if (c == '\r') {
setCursorCol(0);
clearParsedLine();
updateVisibility(true, true, true);
return;
}
if (m->cursor_moved_by_mouse) {
moveToBottom();
}
if (c == 0x1b || m->escape_sequence.isActive()) {
m->escape_sequence.write(c);
return;
}
}
bool ok = !(isTerminalMode() && by_keyboard);
if (c < 0x20) {
if (c == 0x08) {
if (ok) {
doBackspace();
}
} else if (c == 0x09) {
if (ok) {
ushort u = c;
internalWrite(&u, &u + 1);
}
} else if (c == 0x0a) {
if (ok) {
pressEnter();
}
} else if (c == 0x0d) {
if (ok) {
writeCR();
}
} else if (c == 0x1b) {
pressEscape();
} else if (c >= 1 && c <= 26) {
pressLetterWithControl(c);
}
} else if (c == 0x7f) {
if (ok) {
doDelete();
}
} else if (c < 0x10000) {
if (ok) {
ushort u = c;
internalWrite(&u, &u + 1);
}
} else if (c >= 0x10000 && c <= 0x10ffff) {
if (ok) {
ushort t[2];
t[0] = (c - 0x10000) / 0x400 + 0xd800;
t[1] = (c - 0x10000) % 0x400 + 0xdc00;
internalWrite(t, t + 2);
}
} else {
switch (c) {
case EscapeCode::Up:
if (ok) moveCursorUp();
break;
case EscapeCode::Down:
if (ok) moveCursorDown();
break;
case EscapeCode::Right:
if (ok) moveCursorRight();
break;
case EscapeCode::Left:
if (ok) moveCursorLeft();
break;
case EscapeCode::Home:
if (ok) moveCursorHome();
break;
case EscapeCode::End:
if (ok) moveCursorEnd();
break;
case EscapeCode::PageUp:
if (ok) movePageUp();
break;
case EscapeCode::PageDown:
if (ok) movePageDown();
break;
case EscapeCode::Insert:
clearShiftModifier();
break;
case EscapeCode::Delete:
clearShiftModifier();
if (ok) doDelete();
break;
}
}
}
void AbstractCharacterBasedApplication::write(char const *ptr, int len, bool by_keyboard)
{
if (isReadOnly()) return;
char const *begin = ptr;
char const *end = begin + (len < 0 ? strlen(ptr) : len);
char const *left = begin;
char const *right = begin;
while (1) {
int c = -1;
if (right < end) {
c = *right & 0xff;
}
if (c == '\n' || c == '\r' || c < 0) {
utf8 src(left, right);
while (1) {
int d = src.next();
if (d == 0) break;
write(d, by_keyboard);
}
if (c < 0) break;
right++;
if (c == '\r') {
c = isInsertMode() ? '\n' : '\r';
if (right < end && *right == '\n') {
c = '\n';
right++;
}
write(c, by_keyboard);
} else if (c == '\n') {
write('\n', by_keyboard);
}
left = right;
} else {
right++;
}
}
}
-void AbstractCharacterBasedApplication::write(const std::string &text)
+void AbstractCharacterBasedApplication::write(std::string const &text)
{
if (!text.empty()) {
write(text.c_str(), text.size(), false);
}
}
void AbstractCharacterBasedApplication::write_(char const *ptr, bool by_keyboard)
{
write(ptr, -1, by_keyboard);
}
void AbstractCharacterBasedApplication::write_(QString const &text, bool by_keyboard)
{
if (isReadOnly()) return;
if (text.size() == 1) {
ushort c = text.at(0).unicode();
write(c, by_keyboard);
return;
}
int len = text.size();
if (len > 0) {
ushort const *begin = text.utf16();
ushort const *end = begin + len;
ushort const *left = begin;
ushort const *right = begin;
while (1) {
int c = -1;
if (right < end) {
c = *right;
}
if (c < 0x20) {
if (left < right) {
internalWrite(left, right);
}
if (c == -1) break;
right++;
if (c == '\n' || c == '\r') {
if (c == '\r') {
if (right < end && *right == '\n') {
right++;
}
}
writeNewLine();
} else {
write(c, by_keyboard);
}
left = right;
} else {
right++;
}
}
}
}
void AbstractCharacterBasedApplication::write(QKeyEvent *e)
{
setModifierKeys(e->modifiers());
int c = e->key();
if (c == Qt::Key_Backspace) {
write(0x08, true);
} else if (c == Qt::Key_Delete) {
write(0x7f, true);
} else if (c == Qt::Key_Up) {
if (isControlModifierPressed()) {
scrollUp();
} else {
write(EscapeCode::Up, true);
}
} else if (c == Qt::Key_Down) {
if (isControlModifierPressed()) {
scrollDown();
} else {
write(EscapeCode::Down, true);
}
} else if (c == Qt::Key_Left) {
write(EscapeCode::Left, true);
} else if (c == Qt::Key_Right) {
write(EscapeCode::Right, true);
} else if (c == Qt::Key_PageUp) {
write(EscapeCode::PageUp, true);
} else if (c == Qt::Key_PageDown) {
write(EscapeCode::PageDown, true);
} else if (c == Qt::Key_Home) {
if (isControlModifierPressed()) {
moveToTop();
} else {
write(EscapeCode::Home, true);
}
} else if (c == Qt::Key_End) {
if (isControlModifierPressed()) {
moveToBottom();
} else {
write(EscapeCode::End, true);
}
} else if (c == Qt::Key_Return || c == Qt::Key_Enter) {
write('\n', true);
} else if (c == Qt::Key_Escape) {
write(0x1b, true);
} else if (isControlModifierPressed()) {
if (QChar(c).isLetter()) {
c = QChar(c).toUpper().unicode();
if (c >= 0x40 && c < 0x60) {
write(c - 0x40, true);
}
}
} else {
QString text = e->text();
write_(text, true);
}
}
void Document::retrieveLastText(std::vector<char> *out, int maxlen) const
{
int remain = maxlen;
int i = lines.size();
while (i > 0 && remain > 0) {
i--;
QByteArray const &data = lines[i].text;
int n = data.size();
if (n > remain) {
n = remain;
}
char const *p = data.data() + data.size() - n;
out->insert(out->begin(), p, p + n);
remain -= n;
}
}
diff --git a/src/texteditor/AbstractCharacterBasedApplication.h b/src/texteditor/AbstractCharacterBasedApplication.h
index 6a6dc7a..ad8bfdb 100644
--- a/src/texteditor/AbstractCharacterBasedApplication.h
+++ b/src/texteditor/AbstractCharacterBasedApplication.h
@@ -1,421 +1,421 @@
#ifndef ABSTRACTCHARACTERBASEDAPPLICATION_H
#define ABSTRACTCHARACTERBASEDAPPLICATION_H
#include <QList>
#include <QRect>
#include <QString>
#include <QByteArray>
#include <memory>
#include <vector>
#include <functional>
#include <QKeyEvent>
#include <QColor>
namespace EscapeCode {
enum EscapeCode {
Up = 0x1b5b4100,
Down = 0x1b5b4200,
Right = 0x1b5b4300,
Left = 0x1b5b4400,
Home = 0x1b4f4800,
End = 0x1b4f4600,
Insert = 0x1b5b327e,
Delete = 0x1b5b337e,
PageUp = 0x1b5b357e,
PageDown = 0x1b5b367e,
};
}
class Document {
public:
struct CharAttr_ {
size_t offset = 0;
int color = -1;
};
struct Line {
enum Type {
Unknown,
Normal,
Add,
Del,
};
int type = Unknown;
int hunk_number = -1;
int line_number = 0;
size_t byte_offset = 0;
QByteArray text;
Line() = default;
explicit Line(QByteArray const &ba)
: type(Normal)
, text(ba)
{
}
explicit Line(std::string const &str, Type type = Normal)
: type(type)
{
text.append(str.c_str(), str.size());
}
};
QList<Line> lines;
void retrieveLastText(std::vector<char> *out, int maxlen) const;
};
class TextEditorEngine {
public:
Document document;
TextEditorEngine()
{
document.lines.push_back(Document::Line(QByteArray()));
}
};
struct SelectionAnchor {
enum Enabled {
No,
EnabledHard,
EnabledEasy,
};
Enabled enabled = No;
int row = 0;
int col = 0;
int compare(SelectionAnchor const &a) const
{
if (enabled && a.enabled) {
if (row < a.row) return -1;
if (row > a.row) return 1;
if (col < a.col) return -1;
if (col > a.col) return 1;
} else {
if (a.enabled) return -1;
if (enabled) return 1;
}
return 0;
}
bool operator == (SelectionAnchor const &a) const
{
return compare(a) == 0;
}
bool operator != (SelectionAnchor const &a) const
{
return compare(a) != 0;
}
bool operator < (SelectionAnchor const &a) const
{
return compare(a) < 0;
}
bool operator > (SelectionAnchor const &a) const
{
return compare(a) > 0;
}
bool operator <= (SelectionAnchor const &a) const
{
return compare(a) <= 0;
}
bool operator >= (SelectionAnchor const &a) const
{
return compare(a) >= 0;
}
};
using TextEditorEnginePtr = std::shared_ptr<TextEditorEngine>;
struct TextEditorContext {
QRect cursor_rect;
bool single_line = false;
int current_row = 0;
int current_col = 0;
int current_col_hint = 0;
int saved_row = 0;
int saved_col = 0;
int saved_col_hint = 0;
int current_char_span = 1;
int scroll_row_pos = 0;
int scroll_col_pos = 0;
int viewport_org_x = 0;
int viewport_org_y = 1;
int viewport_width = 80;
int viewport_height = 23;
int tab_span = 4;
int bottom_line_y = -1;
TextEditorEnginePtr engine;
};
using DialogHandler = std::function<void (bool, QString const &)>;
class AbstractCharacterBasedApplication {
public:
static const int LEFT_MARGIN = 8;
static const int RIGHT_MARGIN = 10;
enum class WriteMode {
Insert,
Overwrite,
};
enum class State {
Normal,
Exit,
};
struct CharAttr {
uint16_t index;
uint16_t flags = 0;
QColor color;
CharAttr(int index = Normal)
: index(index)
{
}
bool operator == (CharAttr const &r) const
{
return index == r.index && color == r.color;
}
bool operator != (CharAttr const &r) const
{
return !operator == (r);
}
enum Index {
Normal,
Invert,
Hilite,
};
enum Flag {
Selected = 0x0001,
CurrentLine = 0x0002,
};
};
struct Option {
CharAttr char_attr;
QRect clip;
};
struct Character {
uint16_t c = 0;
CharAttr a;
};
enum LineFlag {
LineChanged = 1,
};
static int charWidth(uint32_t c);
class FormattedLine {
public:
QString text;
enum Attr {
StyleID = 0x00ffffff,
Selected = 0x01000000,
};
uint32_t atts;
FormattedLine(QString const &text, int atts)
: text(text)
, atts(atts)
{
}
bool isSelected() const
{
return atts & Selected;
}
};
QList<FormattedLine> formatLine(const Document::Line &line, int tab_span, int anchor_a = -1, int anchor_b = -1) const;
private:
struct Private;
Private *m;
protected:
SelectionAnchor selection_anchor_0;
SelectionAnchor selection_anchor_1;
protected:
std::vector<Character> *screen();
std::vector<Character> const *screen() const;
std::vector<uint8_t> *line_flags();
void initEditor();
void fetchCurrentLine() const;
void clearParsedLine();
int cursorX() const;
int cursorY() const;
int editorViewportWidth() const;
int editorViewportHeight() const;
virtual int print(int x, int y, QString const &text, Option const &opt);
std::shared_ptr<TextEditorContext> editor_cx;
std::shared_ptr<TextEditorContext> dialog_cx;
TextEditorContext *cx();
TextEditorContext const *cx() const;
Document *document();
int documentLines() const;
bool isSingleLineMode() const;
void ensureCurrentLineVisible();
int decideColumnScrollPos() const;
int calcVisualWidth(Document::Line const &line) const;
int leftMargin_() const;
void makeBuffer();
int printArea(const TextEditorContext *cx, SelectionAnchor const *sel_a = nullptr, SelectionAnchor const *sel_b = nullptr);
int calcIndexToColumn(const std::vector<uint32_t> &vec, int index) const;
virtual void updateVisibility(bool ensure_current_line_visible, bool change_col, bool auto_scroll) = 0;
void commitLine(const std::vector<uint32_t> &vec);
void doDelete();
void doBackspace();
bool isDialogMode();
void setDialogMode(bool f);
void closeDialog(bool result);
void setDialogOption(QString const &title, QString const &value, DialogHandler handler);
void execDialog(QString const &dialog_title, QString dialog_value, DialogHandler handler);
void toggleSelectionAnchor();
private:
int internalParseLine(std::vector<uint32_t> *vec, int increase_hint) const;
void internalWrite(const ushort *begin, const ushort *end);
void pressLetterWithControl(int c);
void invalidateAreaBelowTheCurrentLine();
void onQuit();
void onOpenFile();
void onSaveFile();
void printInvertedBar(int x, int y, char const *text, int padchar);
SelectionAnchor currentAnchor(SelectionAnchor::Enabled enabled);
enum class EditOperation {
Cut,
Copy,
};
void editSelected(EditOperation op, std::vector<uint32_t> *cutbuffer);
void deselect();
int calcColumnToIndex(int column);
void edit_(EditOperation op);
bool isCurrentLineWritable() const;
void initEngine(const std::shared_ptr<TextEditorContext>& cx);
void writeCR();
bool deleteIfSelected();
static int findSyntax(const QList<Document::CharAttr_> *list, size_t offset);
static void insertSyntax(QList<Document::CharAttr_> *list, size_t offset, const Document::CharAttr_ &a);
protected:
void parseLine(std::vector<uint32_t> *vec, int increase_hint, bool force);
QByteArray parsedLine() const;
void setCursorRow(int row, bool auto_scroll = true, bool by_mouse = false);
void setCursorCol(int col, bool auto_scroll = true, bool by_mouse = false);
void setCursorPos(int row, int col);
void setCursorColByIndex(const std::vector<uint32_t> &vec, int col_index);
int nextTabStop(int x) const;
int scrollBottomLimit() const;
bool isPaintingSuppressed() const;
void setPaintingSuppressed(bool f);
void scrollRight();
void scrollLeft();
void addNewLineToBottom();
void appendNewLine(std::vector<uint32_t> *vec);
void writeNewLine();
void updateCursorPos(bool auto_scroll);
QString statusLine() const;
void preparePaintScreen();
void setRecentlyUsedPath(QString const &path);
QString recentlyUsedPath();
void clearRect(int x, int y, int w, int h);
void paintLineNumbers(std::function<void(int, QString, Document::Line const *line)> const &draw);
bool isAutoLayout() const;
void invalidateArea(int top_y = 0);
void savePos();
void restorePos();
public:
virtual void layoutEditor();
void scrollUp();
void scrollDown();
void moveCursorOut();
void moveCursorHome();
void moveCursorEnd();
void moveCursorUp();
void moveCursorDown();
void moveCursorLeft();
void moveCursorRight();
void movePageUp();
void movePageDown();
void scrollToTop();
AbstractCharacterBasedApplication();
virtual ~AbstractCharacterBasedApplication();
TextEditorEnginePtr engine();
int screenWidth() const;
int screenHeight() const;
void setScreenSize(int w, int h, bool update_layout);
void setTextEditorEngine(const TextEditorEnginePtr &e);
void openFile(QString const &path);
void saveFile(QString const &path);
void loadExampleFile();
void pressEnter();
void pressEscape();
State state() const;
bool isLineNumberVisible() const;
void showLineNumber(bool show, int left_margin = LEFT_MARGIN);
void showHeader(bool f);
void showFooter(bool f);
void setAutoLayout(bool f);
void setDocument(const QList<Document::Line> *source);
void setSelectionAnchor(SelectionAnchor::Enabled enabled, bool update_anchor, bool auto_scroll);
bool isSelectionAnchorEnabled() const;
void setNormalTextEditorMode(bool f);
void setToggleSelectionAnchorEnabled(bool f);
void setReadOnly(bool f);
bool isReadOnly() const;
void editPaste();
void editCopy();
void editCut();
void setWriteMode(WriteMode wm);
bool isInsertMode() const;
bool isOverwriteMode() const;
void setTerminalMode(bool f);
bool isTerminalMode() const;
void moveToTop();
void moveToBottom();
bool isBottom() const;
void setLineMargin(int n);
void write(uint32_t c, bool by_keyboard);
void write(char const *ptr, int len, bool by_keyboard);
void write(std::string const &text);
void write(QKeyEvent *e);
void setTextCodec(QTextCodec *codec);
void setCursorVisible(bool show);
bool isCursorVisible();
void setModifierKeys(Qt::KeyboardModifiers keymod);
bool isControlModifierPressed() const;
bool isShiftModifierPressed() const;
void clearShiftModifier();
void retrieveLastText(std::vector<char> *out, int maxlen) const;
bool isChanged() const;
void setChanged(bool f);
void logicalMoveToBottom();
protected:
void write_(char const *ptr, bool by_keyboard);
- void write_(const QString &text, bool by_keyboard);
+ void write_(QString const &text, bool by_keyboard);
void makeColumnPosList(std::vector<int> *out);
};
class AbstractTextEditorApplication : public AbstractCharacterBasedApplication {
};
#endif // ABSTRACTCHARACTERBASEDAPPLICATION_H
diff --git a/src/unix/UnixProcess.h b/src/unix/UnixProcess.h
index 9de5814..801a457 100644
--- a/src/unix/UnixProcess.h
+++ b/src/unix/UnixProcess.h
@@ -1,33 +1,33 @@
#ifndef UNIXPROCESS_H
#define UNIXPROCESS_H
#include <vector>
#include <string>
#include <QString>
#include <vector>
#include <deque>
#include <list>
class UnixProcess {
private:
struct Private;
Private *m;
public:
std::vector<char> outbytes;
std::vector<char> errbytes;
UnixProcess();
~UnixProcess();
QString outstring();
QString errstring();
- static void parseArgs(const std::string &cmd, std::vector<std::string> *out);
+ static void parseArgs(std::string const &cmd, std::vector<std::string> *out);
void start(QString const &command, bool use_input);
int wait();
void writeInput(char const *ptr, int len);
void closeInput(bool justnow);
};
#endif // UNIXPROCESS_H
diff --git a/src/unix/UnixPtyProcess.cpp b/src/unix/UnixPtyProcess.cpp
index 2e1d18a..33996d5 100644
--- a/src/unix/UnixPtyProcess.cpp
+++ b/src/unix/UnixPtyProcess.cpp
@@ -1,239 +1,240 @@
#include "UnixPtyProcess.h"
#include <unistd.h>
#include <cstdlib>
#include <csignal>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <deque>
#include <QMutex>
#include <QDir>
namespace {
void make_argv(char *command, std::vector<char *> *out)
{
char *dst = command;
char *src = command;
char *arg = command;
bool quote = false;
bool accept = false;
while (1) {
char c = *src;
if (c == '\"') {
if (src[1] == '\"' && src[2] == '\"') {
*dst++ = c;
src += 3;
} else {
quote = !quote;
accept = true;
src++;
}
} else if (quote && c != 0) {
*dst++ = *src++;
} else if (c == 0 || isspace(c & 0xff)) {
*dst++ = 0;
if (accept || *arg) {
out->push_back(arg);
}
if (c == 0) break;
accept = false;
arg = dst;
src++;
} else {
*dst++ = *src++;
}
}
}
class OutputReaderThread : public QThread {
private:
QMutex *mutex;
int pty_master;
std::deque<char> *output_queue;
std::vector<char> *output_vector;
protected:
void run()
{
while (1) {
if (isInterruptionRequested()) break;
char buf[1024];
int len = read(pty_master, buf, sizeof(buf));
if (len < 1) break;
{
QMutexLocker lock(mutex);
output_queue->insert(output_queue->end(), buf, buf + len);
output_vector->insert(output_vector->end(), buf, buf + len);
}
}
}
public:
void start(QMutex *mutex, int pty_master, std::deque<char> *outq, std::vector<char> *outv)
{
this->mutex = mutex;
this->pty_master = pty_master;
this->output_queue = outq;
this->output_vector = outv;
QThread::start();
}
};
} // namespace
// UnixPtyProcess
struct UnixPtyProcess::Private {
QMutex mutex;
std::string command;
int pty_master;
std::deque<char> output_queue;
std::vector<char> output_vector;
OutputReaderThread th_output_reader;
int exit_code = -1;
};
UnixPtyProcess::UnixPtyProcess()
: m(new Private)
{
}
UnixPtyProcess::~UnixPtyProcess()
{
stop();
delete m;
}
bool UnixPtyProcess::isRunning() const
{
return QThread::isRunning();
}
void UnixPtyProcess::writeInput(char const *ptr, int len)
{
int r = write(m->pty_master, ptr, len);
(void)r;
}
int UnixPtyProcess::readOutput(char *ptr, int len)
{
QMutexLocker lock(&m->mutex);
int n = m->output_queue.size();
if (n > len) {
n = len;
}
if (n > 0) {
auto it = m->output_queue.begin();
std::copy(it, it + n, ptr);
m->output_queue.erase(it, it + n);
}
return n;
}
-void UnixPtyProcess::start(QString const &cmd)
+void UnixPtyProcess::start(QString const &cmd, QVariant const &userdata)
{
if (isRunning()) return;
m->command = cmd.toStdString();
+ user_data = userdata;
QThread::start();
}
bool UnixPtyProcess::wait(unsigned long time)
{
return QThread::wait(time);
}
void UnixPtyProcess::run()
{
struct termios orig_termios;
struct winsize orig_winsize;
tcgetattr(STDIN_FILENO, &orig_termios);
ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&orig_winsize);
m->pty_master = posix_openpt(O_RDWR);
grantpt(m->pty_master);
unlockpt(m->pty_master);
pid_t pid = fork();
if (pid == 0) {
setsid();
putenv(const_cast<char *>("LANG=C"));
char *pts_name = ptsname(m->pty_master);
int pty_slave = open(pts_name, O_RDWR);
close(m->pty_master);
struct termios tio;
memset(&tio, 0, sizeof(tio));
cfmakeraw(&tio);
tio.c_cc[VMIN] = 1;
tio.c_cc[VTIME] = 0;
tio.c_lflag |= ECHO;
tcsetattr(pty_slave, TCSANOW, &tio);
ioctl(pty_slave, TIOCSWINSZ, &orig_winsize);
dup2(pty_slave, STDIN_FILENO);
dup2(pty_slave, STDOUT_FILENO);
dup2(pty_slave, STDERR_FILENO);
close(pty_slave);
QDir::setCurrent(change_dir);
char *command = (char *)alloca(m->command.size() + 1);
strcpy(command, m->command.c_str());
std::vector<char *> argv;
make_argv(command, &argv);
argv.push_back(nullptr);
execvp(argv[0], &argv[0]);
} else {
m->th_output_reader.start(&m->mutex, m->pty_master, &m->output_queue, &m->output_vector);
while (1) {
if (isInterruptionRequested()) break;
int status = 0;
int r = waitpid(pid, &status, WNOHANG);
if (r < 0) break;
QThread::currentThread()->msleep(1);
if (r > 0) {
if (WIFEXITED(status)) {
m->exit_code = WEXITSTATUS(status);
break;
}
if (WIFSIGNALED(status)) {
break;
}
}
}
kill(pid, SIGTERM);
m->th_output_reader.requestInterruption();
m->th_output_reader.wait();
close(m->pty_master);
m->pty_master = -1;
- emit completed();
+ emit completed(user_data);
}
}
void UnixPtyProcess::stop()
{
requestInterruption();
wait();
}
int UnixPtyProcess::getExitCode() const
{
return m->exit_code;
}
QString UnixPtyProcess::getMessage() const
{
QString s;
if (!m->output_vector.empty()) {
s = QString::fromUtf8(&m->output_vector[0], m->output_vector.size());
}
return s;
}
diff --git a/src/unix/UnixPtyProcess.h b/src/unix/UnixPtyProcess.h
index 98237b1..9bcabb0 100644
--- a/src/unix/UnixPtyProcess.h
+++ b/src/unix/UnixPtyProcess.h
@@ -1,26 +1,26 @@
#ifndef UNIXPTYPROCESS_H
#define UNIXPTYPROCESS_H
#include "AbstractProcess.h"
#include <QThread>
class UnixPtyProcess : public AbstractPtyProcess, public QThread {
private:
struct Private;
Private *m;
protected:
void run();
public:
UnixPtyProcess();
~UnixPtyProcess();
bool isRunning() const;
void writeInput(char const *ptr, int len);
int readOutput(char *ptr, int len);
- void start(QString const &cmd);
+ void start(QString const &cmd, QVariant const &userdata);
bool wait(unsigned long time = ULONG_MAX);
void stop();
int getExitCode() const;
QString getMessage() const;
};
#endif // UNIXPTYPROCESS_H
diff --git a/src/webclient.cpp b/src/webclient.cpp
index 5643da6..6c8172e 100644
--- a/src/webclient.cpp
+++ b/src/webclient.cpp
@@ -1,1083 +1,1083 @@
#include "webclient.h"
#include <cstring>
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#if USE_OPENSSL
#pragma comment(lib, "libeay32.lib")
#pragma comment(lib, "ssleay32.lib")
#endif
typedef SOCKET socket_t;
#pragma warning(disable:4996)
#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netdb.h>
#define closesocket(S) ::close(S)
typedef int socket_t;
#define INVALID_SOCKET (-1)
#define SOCKET_ERROR (-1)
#define stricmp(A, B) strcasecmp(A, B)
#define strnicmp(A, B, C) strncasecmp(A, B, C)
#endif
#if USE_OPENSSL
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/x509.h>
#else
typedef void SSL;
typedef void SSL_CTX;
#endif
#include <cassert>
#include "charvec.h"
#define USER_AGENT "Generic Web Client"
struct WebContext::Private {
SSL_CTX *ctx = nullptr;
bool use_keep_alive = false;
WebProxy http_proxy;
WebProxy https_proxy;
};
WebClient::URL::URL(std::string const &addr)
{
data.full_request = addr;
char const *str = addr.c_str();
char const *left;
char const *right;
left = str;
right = strstr(left, "://");
if (right) {
data.scheme.assign(str, right - str);
left = right + 3;
}
right = strchr(left, '/');
if (!right) {
right = left + strlen(left);
}
if (right) {
char const *p = strchr(left, ':');
if (p && left < p && p < right) {
int n = 0;
char const *q = p + 1;
while (q < right) {
if (isdigit(*q & 0xff)) {
n = n * 10 + (*q - '0');
} else {
n = -1;
break;
}
q++;
}
data.host.assign(left, p - left);
if (n > 0 && n < 65536) {
data.port = n;
}
} else {
data.host.assign(left, right - left);
}
data.path = right;
}
}
bool WebClient::URL::isssl() const
{
if (scheme() == "https") return true;
if (scheme() == "http") return false;
if (port() == 443) return true;
return false;
}
-void WebClientHandler::abort(const std::string &message)
+void WebClientHandler::abort(std::string const &message)
{
throw WebClient::Error(message);
}
struct WebClient::Private {
std::vector<std::string> request_header;
Error error;
Response response;
WebContext *webcx;
int crlf_state = 0;
size_t content_offset = 0;
std::string last_host_name;
int last_port = 0;
bool keep_alive = false;
socket_t sock = INVALID_SOCKET;
SSL *ssl = nullptr;
};
WebClient::WebClient(WebContext *webcx)
: m(new Private)
{
assert(webcx);
m->webcx = webcx;
}
WebClient::~WebClient()
{
close();
delete m;
}
void WebClient::initialize()
{
#ifdef _WIN32
WSADATA wsaData;
WORD wVersionRequested;
wVersionRequested = MAKEWORD(1, 1);
WSAStartup(wVersionRequested, &wsaData);
atexit(cleanup);
#endif
#if USE_OPENSSL
OpenSSL_add_all_algorithms();
#endif
}
void WebClient::cleanup()
{
#if USE_OPENSSL
ERR_free_strings();
#endif
#ifdef _WIN32
WSACleanup();
#endif
}
void WebClient::reset()
{
m->request_header.clear();
m->error = Error();
m->response = Response();
m->crlf_state = 0;
m->content_offset = 0;
}
void WebClient::output_debug_string(char const *str)
{
if (0) {
#ifdef _WIN32
OutputDebugStringA(str);
#else
fwrite(str, 1, strlen(str), stderr);
#endif
}
}
void WebClient::output_debug_strings(std::vector<std::string> const &vec)
{
for (std::string const &s : vec) {
output_debug_string((s + '\n').c_str());
}
}
WebClient::Error const &WebClient::error() const
{
return m->error;
}
void WebClient::clear_error()
{
m->error = Error();
}
int WebClient::get_port(URL const *url, char const *scheme, char const *protocol)
{
int port = url->port();
if (port < 1 || port > 65535) {
struct servent *s;
s = getservbyname(url->scheme().c_str(), protocol);
if (s) {
port = ntohs(s->s_port);
} else {
s = getservbyname(scheme, protocol);
if (s) {
port = ntohs(s->s_port);
}
}
if (port < 1 || port > 65535) {
port = 80;
}
}
return port;
}
static inline std::string to_s(size_t n)
{
char tmp[100];
sprintf(tmp, "%u", (int)n);
return tmp;
}
void WebClient::set_default_header(URL const &url, Post const *post, RequestOption const &opt)
{
std::vector<std::string> header;
auto AddHeader = [&](std::string const &s){
header.push_back(s);
};
AddHeader("Host: " + url.host());
AddHeader("User-Agent: " USER_AGENT);
AddHeader("Accept: */*");
if (opt.keep_alive) {
AddHeader("Connection: keep-alive");
} else {
AddHeader("Connection: close");
}
if (post) {
AddHeader("Content-Length: " + to_s(post->data.size()));
std::string ct = "Content-Type: ";
if (post->content_type.empty()) {
ct += "application/octet-stream";
} else if (post->content_type == CT_MULTIPART_FORM_DATA) {
ct += post->content_type;
if (!post->boundary.empty()) {
ct += "; boundary=";
ct += post->boundary;
}
} else {
ct += post->content_type;
}
AddHeader(ct);
}
header.insert(header.end(), m->request_header.begin(), m->request_header.end());
m->request_header = std::move(header);
}
std::string WebClient::make_http_request(URL const &url, Post const *post, WebProxy const *proxy, bool https)
{
std::string str;
str = post ? "POST " : "GET ";
if (proxy && !https) {
str += url.data.full_request;
str += " HTTP/1.0";
str += "\r\n";
} else {
str += url.path();
str += " HTTP/1.0";
str += "\r\n";
}
for (std::string const &s: m->request_header) {
str += s;
str += "\r\n";
}
str += "\r\n";
return str;
}
void WebClient::parse_http_header(char const *begin, char const *end, std::vector<std::string> *header)
{
if (begin < end) {
char const *left = begin;
char const *right = left;
while (1) {
if (right >= end) {
break;
}
if (*right == '\r' || *right == '\n') {
if (left < right) {
header->push_back(std::string(left, right));
}
if (right + 1 < end && *right == '\r' && right[1] == '\n') {
right++;
}
right++;
if (*right == '\r' || *right == '\n') {
if (right + 1 < end && *right == '\r' && right[1] == '\n') {
right++;
}
right++;
left = right;
break;
}
left = right;
} else {
right++;
}
}
}
}
void WebClient::parse_http_header(char const *begin, char const *end, WebClient::Response *out)
{
*out = Response();
parse_http_header(begin, end, &out->header);
parse_header(&out->header, out);
}
static void send_(socket_t s, char const *ptr, int len)
{
while (len > 0) {
int n = send(s, ptr, len, 0);
if (n < 1 || n > len) {
throw WebClient::Error("send request failed.");
}
ptr += n;
len -= n;
}
}
void WebClient::on_end_header(std::vector<char> const *vec, WebClientHandler *handler)
{
if (vec->empty()) return;
char const *begin = &vec->at(0);
char const *end = begin + vec->size();
parse_http_header(begin, end, &m->response);
if (handler) {
handler->checkHeader(this);
}
}
void WebClient::append(char const *ptr, size_t len, std::vector<char> *out, WebClientHandler *handler)
{
size_t offset = out->size();
out->insert(out->end(), ptr, ptr + len);
if (m->crlf_state < 0) {
// nop
} else {
for (size_t i = 0; i < len; i++) {
int c = ptr[i] & 0xff;
if (c == '\r') {
m->crlf_state |= 1;
} else if (c == '\n') {
m->crlf_state |= 1;
m->crlf_state++;
} else {
m->crlf_state = 0;
}
if (m->crlf_state == 4) {
m->content_offset = offset + i + 1;
on_end_header(out, handler);
m->crlf_state = -1;
break;
}
}
}
if (handler && m->content_offset > 0) {
offset = out->size();
if (offset > m->content_offset) {
size_t len = offset - m->content_offset;
char const *ptr = &out->at(m->content_offset);
handler->checkContent(ptr, len);
}
}
}
static char *stristr(char *str1, char const *str2)
{
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
for (size_t i = 0; i + len2 <= len1; i++) {
if (strnicmp(str1 + i, str2, len2) == 0) {
return str1 + i;
}
}
return nullptr;
}
class ResponseHeader {
public:
size_t pos = 0;
std::vector<char> line;
int content_length = -1;
bool connection_keep_alive = false;
bool connection_close = false;
int lf = 0;
enum State {
Header,
Content,
};
State state = Header;
void put(int c)
{
pos++;
if (state == Header) {
if (c== '\r' || c == '\n') {
if (!line.empty()) {
line.push_back(0);
char *begin = &line[0];
char *p = strchr(begin, ':');
if (p && *p == ':') {
*p++ = 0;
auto IS = [&](char const *name){ return stricmp(begin, name) == 0; };
if (IS("content-length")) {
content_length = strtol(p, nullptr, 10);
} else if (IS("connection")) {
if (stristr(p, "keep-alive")) {
connection_keep_alive = true;
} else if (stristr(p, "close")) {
connection_close = true;
}
}
}
line.clear();
}
if (c== '\r') {
return;
}
if (c == '\n') {
lf++;
if (lf == 2) {
state = Content;
}
return;
}
}
lf = 0;
line.push_back(c);
}
}
};
void WebClient::receive_(RequestOption const &opt, std::function<int(char *, int)> rcv, std::vector<char> *out)
{
char buf[4096];
size_t pos = 0;
ResponseHeader rh;
while (1) {
int n;
if (rh.state == ResponseHeader::Content && rh.content_length >= 0) {
n = rh.pos + rh.content_length - pos;
if (n > (int)sizeof(buf)) {
n = sizeof(buf);
}
if (n < 1) break;
} else {
n = sizeof(buf);
}
n = rcv(buf, n);
if (n < 1) break;
if (0) { // debug
fwrite(buf, 1, n, stderr);
}
append(buf, n, out, opt.handler);
pos += n;
if (rh.state == ResponseHeader::Header) {
for (int i = 0; i < n; i++) {
rh.put(buf[i]);
if (rh.state == ResponseHeader::Content) {
m->keep_alive = rh.connection_keep_alive && !rh.connection_close;
break;
}
}
}
}
}
bool WebClient::http_get(URL const &request_url, Post const *post, RequestOption const &opt, std::vector<char> *out)
{
clear_error();
out->clear();
URL server_url;
WebProxy const *proxy = m->webcx->http_proxy();
if (proxy) {
server_url = URL(proxy->server);
} else {
server_url = request_url;
}
std::string hostname = server_url.host();
int port = get_port(&server_url, "http", "tcp");
m->keep_alive = opt.keep_alive && hostname == m->last_host_name && port == m->last_port;
if (!m->keep_alive) close();
if (m->sock == INVALID_SOCKET) {
struct hostent *servhost;
struct sockaddr_in server;
servhost = gethostbyname(hostname.c_str());
if (!servhost) {
throw Error("gethostbyname failed.");
}
memset((char *)&server, 0, sizeof(server));
server.sin_family = AF_INET;
memcpy((char *)&server.sin_addr, servhost->h_addr, servhost->h_length);
server.sin_port = htons(port);
m->sock = socket(AF_INET, SOCK_STREAM, 0);
if (m->sock == INVALID_SOCKET) {
throw Error("socket failed.");
}
if (connect(m->sock, (struct sockaddr*) &server, sizeof(server)) == SOCKET_ERROR) {
throw Error("connect failed.");
}
}
m->last_host_name = hostname;
m->last_port = port;
set_default_header(request_url, post, opt);
std::string request = make_http_request(request_url, post, proxy, false);
send_(m->sock, request.c_str(), (int)request.size());
if (post && !post->data.empty()) {
send_(m->sock, (char const *)&post->data[0], (int)post->data.size());
}
m->crlf_state = 0;
m->content_offset = 0;
receive_(opt, [&](char *ptr, int len){
return recv(m->sock, ptr, len, 0);
}, out);
if (!m->keep_alive) close();
return true;
}
bool WebClient::https_get(const URL &request_url, Post const *post, RequestOption const &opt, std::vector<char> *out)
{
#if USE_OPENSSL
if (!m->webcx || !m->webcx->m->ctx) {
output_debug_string("SSL context is null.\n");
return false;
}
auto sslctx = [&](){ return m->webcx->m->ctx; };
clear_error();
out->clear();
auto get_ssl_error = []()->std::string{
char tmp[1000];
unsigned long e = ERR_get_error();
ERR_error_string_n(e, tmp, sizeof(tmp));
return tmp;
};
URL server_url;
WebProxy const *proxy = m->webcx->https_proxy();
if (proxy) {
server_url = URL(proxy->server);
} else {
server_url = request_url;
}
std::string hostname = server_url.host();
int port = get_port(&server_url, "https", "tcp");
m->keep_alive = opt.keep_alive && hostname == m->last_host_name && port == m->last_port;
if (!m->keep_alive) close();
socket_t sock = m->sock;
SSL *ssl = m->ssl;
if (sock == INVALID_SOCKET || !ssl) {
int ret;
struct hostent *servhost;
struct sockaddr_in server;
servhost = gethostbyname(server_url.host().c_str());
if (!servhost) {
throw Error("gethostbyname failed.");
}
memset((char *)&server, 0, sizeof(server));
server.sin_family = AF_INET;
memcpy((char *)&server.sin_addr, servhost->h_addr, servhost->h_length);
server.sin_port = htons(port);
if (sock == INVALID_SOCKET) {
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
throw Error("socket failed.");
}
if (connect(sock, (struct sockaddr*) &server, sizeof(server)) == SOCKET_ERROR) {
throw Error("connect failed.");
}
}
if (proxy) { // testing
char port[10];
sprintf(port, ":%u", get_port(&request_url, "https", "tcp"));
std::string str = "CONNECT ";
str += request_url.data.host;
str += port;
str += " HTTP/1.0\r\n\r\n";
send_(sock, str.c_str(), str.size());
char tmp[1000];
int n = recv(sock, tmp, sizeof(tmp), 0);
int i;
for (i = 0; i < n; i++) {
char c = tmp[i];
if (c < 0x20) break;
}
if (i > 0) {
std::string s(tmp, i);
s = "proxy: " + s + '\n';
#ifdef _WIN32
OutputDebugStringA(s.c_str());
#else
fprintf(stderr, "%s", tmp);
#endif
}
}
ssl = SSL_new(sslctx());
if (!ssl) {
throw Error(get_ssl_error());
}
SSL_set_options(ssl, SSL_OP_NO_SSLv2);
SSL_set_options(ssl, SSL_OP_NO_SSLv3);
ret = SSL_set_fd(ssl, sock);
if (ret == 0) {
throw Error(get_ssl_error());
}
RAND_poll();
while (RAND_status() == 0) {
unsigned short rand_ret = rand() % 65536;
RAND_seed(&rand_ret, sizeof(rand_ret));
}
ret = SSL_connect(ssl);
if (ret != 1) {
throw Error(get_ssl_error());
}
std::string cipher = SSL_get_cipher(ssl);
cipher += '\n';
output_debug_string(cipher.c_str());
std::string version = SSL_get_cipher_version(ssl);
version += '\n';
output_debug_string(version.c_str());
X509 *x509 = SSL_get_peer_certificate(ssl);
if (x509) {
#ifndef OPENSSL_NO_SHA1
std::string fingerprint;
for (int i = 0; i < SHA_DIGEST_LENGTH; i++) {
if (i > 0) {
fingerprint += ':';
}
char tmp[10];
sprintf(tmp, "%02X", x509->sha1_hash[i]);
fingerprint += tmp;
}
fingerprint += '\n';
output_debug_string(fingerprint.c_str());
#endif
long l = SSL_get_verify_result(ssl);
if (l == X509_V_OK) {
// ok
} else {
// wrong
std::string err = X509_verify_cert_error_string(l);
err += '\n';
output_debug_string(err.c_str());
}
std::vector<std::string> vec;
auto GETSTRINGS = [](X509_NAME *x509name, std::vector<std::string> *out){
out->clear();
if (x509name) {
int n = X509_NAME_entry_count(x509name);
for (int i = 0; i < n; i++) {
X509_NAME_ENTRY *entry = X509_NAME_get_entry(x509name, i);
ASN1_STRING *asn1str = X509_NAME_ENTRY_get_data(entry);
int asn1len = ASN1_STRING_length(asn1str);
unsigned char *p = ASN1_STRING_data(asn1str);
std::string str((char const *)p, asn1len);
out->push_back(str);
}
}
};
X509_NAME *subject = X509_get_subject_name(x509);
GETSTRINGS(subject, &vec);
output_debug_string("--- subject ---\n");
output_debug_strings(vec);
X509_NAME *issuer = X509_get_issuer_name(x509);
GETSTRINGS(issuer, &vec);
output_debug_string("--- issuer ---\n");
output_debug_strings(vec);
ASN1_TIME *not_before = X509_get_notBefore(x509);
ASN1_TIME *not_after = X509_get_notAfter(x509);
(void)not_before;
(void)not_after;
X509_free(x509);
} else {
// wrong
}
}
m->last_host_name = hostname;
m->last_port = port;
set_default_header(request_url, post, opt);
std::string request = make_http_request(request_url, post, proxy, true);
auto SEND = [&](char const *ptr, int len){
while (len > 0) {
int n = SSL_write(ssl, ptr, len);
if (n < 1 || n > len) {
throw WebClient::Error(get_ssl_error());
}
ptr += n;
len -= n;
}
};
SEND(request.c_str(), (int)request.size());
if (post && !post->data.empty()) {
SEND((char const *)&post->data[0], (int)post->data.size());
}
m->crlf_state = 0;
m->content_offset = 0;
receive_(opt, [&](char *ptr, int len){
return SSL_read(ssl, ptr, len);
}, out);
m->sock = sock;
m->ssl = ssl;
if (!m->keep_alive) close();
return true;
#endif
return false;
}
void WebClient::get(URL const &url, Post const *post, Response *out, WebClientHandler *handler)
{
reset();
try {
if (!m->webcx->m) {
throw Error("WebContext is null.");
}
RequestOption opt;
opt.keep_alive = m->webcx->m->use_keep_alive;
opt.handler = handler;
std::vector<char> res;
if (url.isssl()) {
#if USE_OPENSSL
https_get(url, post, opt, &res);
#endif
} else {
http_get(url, post, opt, &res);
}
if (!res.empty()) {
char const *begin = &res[0];
char const *end = begin + res.size();
char const *ptr = begin + m->content_offset;
if (ptr < end) {
out->content.assign(ptr, end);
}
}
return;
} catch (Error const &e) {
m->error = e;
close();
}
*out = Response();
}
void WebClient::parse_header(std::vector<std::string> const *header, WebClient::Response *res)
{
if (!header->empty()) {
std::string const &line = header->at(0);
char const *begin = line.c_str();
char const *end = begin + line.size();
if (line.size() > 5 && strncmp(line.c_str(), "HTTP/", 5) == 0) {
int state = 0;
res->version.hi = res->version.lo = res->code = 0;
char const *ptr = begin + 5;
while (1) {
int c = 0;
if (ptr < end) {
c = *ptr & 0xff;
}
switch (state) {
case 0:
if (isdigit(c)) {
res->version.hi = res->version.hi * 10 + (c - '0');
} else if (c == '.') {
state = 1;
} else {
state = -1;
}
break;
case 1:
if (isdigit(c)) {
res->version.lo = res->version.lo * 10 + (c - '0');
} else if (isspace(c)) {
state = 2;
} else {
state = -1;
}
break;
case 2:
if (isspace(c)) {
if (res->code != 0) {
state = -1;
}
} else if (isdigit(c)) {
res->code = res->code * 10 + (c - '0');
} else {
state = -1;
}
break;
default:
state = -1;
break;
}
if (c == 0 || state < 0) {
break;
}
ptr++;
}
}
}
}
std::string WebClient::header_value(std::vector<std::string> const *header, std::string const &name)
{
for (size_t i = 1; i < header->size(); i++) {
std::string const &line = header->at(i);
char const *begin = line.c_str();
char const *end = begin + line.size();
char const *colon = strchr(begin, ':');
if (colon) {
if (strnicmp(begin, name.c_str(), name.size()) == 0) {
char const *ptr = colon + 1;
while (ptr < end && isspace(*ptr & 0xff)) ptr++;
return std::string(ptr, end);
}
}
}
return std::string();
}
std::string WebClient::header_value(std::string const &name) const
{
return header_value(&m->response.header, name);
}
std::string WebClient::content_type() const
{
std::string s = header_value("Content-Type");
char const *begin = s.c_str();
char const *end = begin + s.size();
char const *ptr = begin;
while (ptr < end) {
int c = *ptr & 0xff;
if (c == ';' || c < 0x21) break;
ptr++;
}
if (ptr < end) return std::string(begin, ptr);
return s;
}
size_t WebClient::content_length() const
{
return m->response.content.size();
}
char const *WebClient::content_data() const
{
if (m->response.content.empty()) return "";
return &m->response.content[0];
}
int WebClient::get(URL const &url, WebClientHandler *handler)
{
get(url, nullptr, &m->response, handler);
return m->response.code;
}
int WebClient::post(URL const &url, Post const *post, WebClientHandler *handler)
{
get(url, post, &m->response, handler);
return m->response.code;
}
void WebClient::close()
{
#if USE_OPENSSL
if (m->ssl) {
SSL_shutdown(m->ssl);
SSL_free(m->ssl);
m->ssl = nullptr;
}
#endif
if (m->sock != INVALID_SOCKET) {
shutdown(m->sock, 2); // SD_BOTH or SHUT_RDWR
closesocket(m->sock);
m->sock = INVALID_SOCKET;
}
}
void WebClient::add_header(std::string const &text)
{
m->request_header.push_back(text);
}
WebClient::Response const &WebClient::response() const
{
return m->response;
}
void WebClient::make_application_www_form_urlencoded(char const *begin, char const *end, WebClient::Post *out)
{
*out = WebClient::Post();
out->content_type = CT_APPLICATION_X_WWW_FORM_URLENCODED;
print(&out->data, begin, end - begin);
}
void WebClient::make_multipart_form_data(std::vector<Part> const &parts, WebClient::Post *out, std::string const &boundary)
{
*out = WebClient::Post();
out->content_type = CT_MULTIPART_FORM_DATA;
out->boundary = boundary;
for (Part const &part : parts) {
print(&out->data, "--");
print(&out->data, out->boundary);
print(&out->data, "\r\n");
if (!part.content_disposition.type.empty()) {
ContentDisposition const &cd = part.content_disposition;
std::string s;
s = "Content-Disposition: ";
s += cd.type;
auto Add = [&s](std::string const &name, std::string const &value){
if (!value.empty()) {
s += "; " + name + "=\"";
s += value;
s += '\"';
}
};
Add("name", cd.name);
Add("filename", cd.filename);
print(&out->data, s);
print(&out->data, "\r\n");
}
if (!part.content_type.empty()) {
print(&out->data, "Content-Type: " + part.content_type + "\r\n");
}
if (!part.content_transfer_encoding.empty()) {
print(&out->data, "Content-Transfer-Encoding: " + part.content_transfer_encoding + "\r\n");
}
print(&out->data, "\r\n");
print(&out->data, part.data, part.size);
print(&out->data, "\r\n");
}
print(&out->data, "--");
print(&out->data, out->boundary);
print(&out->data, "--\r\n");
}
void WebClient::make_multipart_form_data(char const *data, size_t size, WebClient::Post *out, std::string const &boundary)
{
Part part;
part.data = data;
part.size = size;
std::vector<Part> parts;
parts.push_back(part);
make_multipart_form_data(parts, out, boundary);
}
//
WebContext::WebContext()
: m(new Private)
{
#if USE_OPENSSL
SSL_load_error_strings();
SSL_library_init();
m->ctx = SSL_CTX_new(SSLv23_client_method());
#endif
}
WebContext::~WebContext()
{
#if USE_OPENSSL
SSL_CTX_free(m->ctx);
#endif
delete m;
}
void WebContext::set_keep_alive_enabled(bool f)
{
m->use_keep_alive = f;
}
-void WebContext::set_http_proxy(const std::string &proxy)
+void WebContext::set_http_proxy(std::string const &proxy)
{
m->http_proxy = WebProxy();
m->http_proxy.server = proxy;
}
-void WebContext::set_https_proxy(const std::string &proxy)
+void WebContext::set_https_proxy(std::string const &proxy)
{
m->https_proxy = WebProxy();
m->https_proxy.server = proxy;
}
const WebProxy *WebContext::http_proxy() const
{
if (!m->http_proxy.empty()) {
return &m->http_proxy;
}
return nullptr;
}
const WebProxy *WebContext::https_proxy() const
{
if (!m->https_proxy.empty()) {
return &m->https_proxy;
}
if (!m->http_proxy.empty()) {
return &m->http_proxy;
}
return nullptr;
}
bool WebContext::load_cacert(char const *path)
{
#if USE_OPENSSL
int r = SSL_CTX_load_verify_locations(m->ctx, path, nullptr);
return r == 1;
#else
return false;
#endif
}
diff --git a/src/webclient.h b/src/webclient.h
index 28d07f9..b99df66 100644
--- a/src/webclient.h
+++ b/src/webclient.h
@@ -1,184 +1,184 @@
#ifndef WEBCLIENT_H_
#define WEBCLIENT_H_
#include <vector>
#include <string>
#include <functional>
#define USE_OPENSSL 1
#define OPENSSL_NO_SHA1 1
class WebContext;
class WebClient;
#define CT_APPLICATION_X_WWW_FORM_URLENCODED "application/x-www-form-urlencoded"
#define CT_MULTIPART_FORM_DATA "multipart/form-data"
class WebClientHandler {
protected:
void abort(std::string const &message = std::string());
public:
virtual ~WebClientHandler() = default;
virtual void checkHeader(WebClient * /*wc*/)
{
}
virtual void checkContent(char const * /*ptr*/, size_t /*len*/)
{
}
};
class WebProxy {
public:
std::string server;
bool empty() const
{
return server.empty();
}
};
class WebClient {
public:
class URL {
friend class WebClient;
private:
struct Data {
std::string full_request;
std::string scheme;
std::string host;
int port = 0;
std::string path;
} data;
public:
URL() = default;
- URL(const std::string &addr);
+ URL(std::string const &addr);
std::string const &scheme() const { return data.scheme; }
std::string const &host() const { return data.host; }
int port() const { return data.port; }
std::string const &path() const { return data.path; }
bool isssl() const;
};
class Error {
private:
std::string msg_;
public:
Error() = default;
Error(std::string const &message)
: msg_(message)
{
}
virtual ~Error() = default;
std::string message() const
{
return msg_;
}
};
struct Response {
int code = 0;
struct Version {
unsigned int hi = 0;
unsigned int lo = 0;
} version;
std::vector<std::string> header;
std::vector<char> content;
};
struct Post {
std::string content_type;
std::string boundary;
std::vector<char> data;
};
struct ContentDisposition {
std::string type;
std::string name;
std::string filename;
};
struct Part {
char const *data = nullptr;
size_t size = 0;
std::string content_type;
ContentDisposition content_disposition;
std::string content_transfer_encoding;
Part() = default;
Part(char const *data, size_t size, std::string const &content_type = std::string())
: data(data)
, size(size)
, content_type(content_type)
{
}
void set_content_disposition(ContentDisposition const &cd)
{
content_disposition = cd;
}
};
struct RequestOption {
WebClientHandler *handler = nullptr;
bool keep_alive = true;
};
private:
struct Private;
Private *m;
void clear_error();
static int get_port(URL const *url, char const *scheme, char const *protocol);
void set_default_header(URL const &url, Post const *post, const RequestOption &opt);
std::string make_http_request(URL const &url, Post const *post, const WebProxy *proxy, bool https);
void parse_http_header(char const *begin, char const *end, std::vector<std::string> *header);
void parse_http_header(char const *begin, char const *end, Response *out);
bool http_get(URL const &request_url, Post const *post, RequestOption const &opt, std::vector<char> *out);
bool https_get(URL const &request_url, Post const *post, RequestOption const &opt, std::vector<char> *out);
void get(URL const &url, Post const *post, Response *out, WebClientHandler *handler);
static void parse_header(std::vector<std::string> const *header, WebClient::Response *res);
- static std::string header_value(std::vector<std::string> const *header, const std::string &name);
+ static std::string header_value(std::vector<std::string> const *header, std::string const &name);
void append(char const *ptr, size_t len, std::vector<char> *out, WebClientHandler *handler);
void on_end_header(const std::vector<char> *vec, WebClientHandler *handler);
void receive_(const RequestOption &opt, std::function<int (char *, int)>, std::vector<char> *out);
void output_debug_string(char const *str);
void output_debug_strings(const std::vector<std::string> &vec);
static void cleanup();
void reset();
public:
static void initialize();
WebClient(WebContext *webcx);
~WebClient();
WebClient(WebClient const &) = delete;
void operator = (WebClient const &) = delete;
Error const &error() const;
int get(URL const &url, WebClientHandler *handler = nullptr);
int post(URL const &url, Post const *post, WebClientHandler *handler = nullptr);
void close();
void add_header(std::string const &text);
Response const &response() const;
std::string header_value(std::string const &name) const;
std::string content_type() const;
size_t content_length() const;
char const *content_data() const;
static void make_application_www_form_urlencoded(char const *begin, char const *end, WebClient::Post *out);
- static void make_multipart_form_data(const std::vector<Part> &parts, WebClient::Post *out, const std::string &boundary);
- static void make_multipart_form_data(char const *data, size_t size, WebClient::Post *out, const std::string &boundary);
+ static void make_multipart_form_data(const std::vector<Part> &parts, WebClient::Post *out, std::string const &boundary);
+ static void make_multipart_form_data(char const *data, size_t size, WebClient::Post *out, std::string const &boundary);
};
class WebContext {
friend class WebClient;
public:
private:
struct Private;
Private *m;
public:
WebContext();
~WebContext();
WebContext(WebContext const &r) = delete;
void operator = (WebContext const &r) = delete;
void set_keep_alive_enabled(bool f);
void set_http_proxy(std::string const &proxy);
void set_https_proxy(std::string const &proxy);
WebProxy const *http_proxy() const;
WebProxy const *https_proxy() const;
bool load_cacert(char const *path);
};
#endif
diff --git a/src/win32/Win32PtyProcess.cpp b/src/win32/Win32PtyProcess.cpp
index df12475..e38edde 100644
--- a/src/win32/Win32PtyProcess.cpp
+++ b/src/win32/Win32PtyProcess.cpp
@@ -1,256 +1,257 @@
#include <windows.h>
#include "Win32PtyProcess.h"
#include <deque>
#include <winpty.h>
#include <QDir>
#include <QMutex>
namespace {
class OutputReaderThread : public QThread {
friend class ::Win32PtyProcess;
private:
public:
HANDLE handle;
std::deque<char> *output_queue = nullptr;
std::vector<char> *output_vector = nullptr;
protected:
void run() override;
public:
void start(HANDLE hOutput, std::deque<char> *outq, std::vector<char> *outv);
};
void OutputReaderThread::run()
{
char buf[1024];
while (1) {
if (isInterruptionRequested()) break;
DWORD amount = 0;
BOOL ret = ReadFile(handle, buf, sizeof(buf), &amount, nullptr);
if (!ret || amount == 0) {
break;
}
output_queue->insert(output_queue->end(), buf, buf + amount);
output_vector->insert(output_vector->end(), buf, buf + amount);
}
}
void OutputReaderThread::start(HANDLE hOutput, std::deque<char> *outq, std::vector<char> *outv)
{
handle = hOutput;
output_queue = outq;
output_vector = outv;
QThread::start();
}
} // namespace
// Win32PtyProcess
struct Win32PtyProcess::Private {
QMutex mutex;
QString command;
std::deque<char> output_queue;
std::vector<char> output_vector;
OutputReaderThread th_output_reader;
HANDLE hProcess = INVALID_HANDLE_VALUE;
HANDLE hOutput = INVALID_HANDLE_VALUE;
HANDLE hInput = INVALID_HANDLE_VALUE;
DWORD exit_code = 0;
};
Win32PtyProcess::Win32PtyProcess()
: m(new Private)
{
}
Win32PtyProcess::~Win32PtyProcess()
{
delete m;
}
bool Win32PtyProcess::isRunning() const
{
return QThread::isRunning();
}
QString Win32PtyProcess::getProgram(QString const &cmdline)
{
ushort const *begin = cmdline.utf16();
ushort const *end = begin + cmdline.size();
ushort const *ptr = begin;
bool quote = 0;
while (1) {
ushort c = 0;
if (ptr < end) {
c = *ptr;
}
if (c == '\"') {
if (quote) {
quote = false;
} else {
quote = true;
}
ptr++;
} else if (quote && c != 0) {
ptr++;
} else if (QChar(c).isSpace() || c == 0) {
break;
} else {
ptr++;
}
}
ushort const *left = begin;
ushort const *right = ptr;
if (left + 1 < right) {
if (left[0] == '\"' && right[-1] == '\"') {
left++;
right--;
}
}
return QString::fromUtf16(left, right - left);
}
void Win32PtyProcess::run()
{
QString program;
program = getProgram(m->command);
QDir::setCurrent(change_dir);
winpty_config_t *agent_cfg = winpty_config_new(WINPTY_FLAG_PLAIN_OUTPUT, nullptr);
winpty_t *pty = winpty_open(agent_cfg, nullptr);
winpty_config_free(agent_cfg);
m->hInput = CreateFileW(winpty_conin_name(pty), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
m->hOutput = CreateFileW(winpty_conout_name(pty), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
m->th_output_reader.start(m->hOutput, &m->output_queue, &m->output_vector);
winpty_spawn_config_t *spawn_cfg = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, (wchar_t const *)program.utf16(), (wchar_t const *)m->command.utf16(), nullptr, nullptr, nullptr);
BOOL spawnSuccess = winpty_spawn(pty, spawn_cfg, &m->hProcess, nullptr, nullptr, nullptr);
if (spawnSuccess) {
while (1) {
if (isInterruptionRequested()) break;
GetExitCodeProcess(m->hProcess, &m->exit_code);
if (m->exit_code == STILL_ACTIVE) {
// running
msleep(1);
} else {
break;
}
}
}
// プロセスの出力を確実に取得するため、ここで output reader スレッドの終了を待つ
m->th_output_reader.wait();
winpty_free(pty);
CloseHandle(m->hInput);
CloseHandle(m->hOutput);
CloseHandle(m->hProcess);
m->hInput = INVALID_HANDLE_VALUE;
m->hOutput = INVALID_HANDLE_VALUE;
m->hProcess = INVALID_HANDLE_VALUE;
- emit completed();
+ emit completed(user_data);
}
int Win32PtyProcess::readOutput(char *dstptr, int maxlen)
{
int len = m->output_queue.size();
if (len > maxlen) {
len = maxlen;
}
if (len > 0) {
auto begin = m->output_queue.begin();
std::copy(begin, begin + len, dstptr);
m->output_queue.erase(begin, begin + len);
}
return len;
}
void Win32PtyProcess::writeInput(char const *ptr, int len)
{
char const *begin = ptr;
char const *end = begin + len;
char const *left = begin;
char const *right = begin;
while (1) {
int c = -1;
if (right < end) {
c = *right & 0xff;
}
if (c == '\r' || c == '\n' || c < 0) {
if (left < right) {
DWORD written;
WriteFile(m->hInput, left, right - left, &written, nullptr);
}
if (c < 0) break;
right++;
if (c == '\r') {
if (*right == '\n') {
right++;
}
c = '\r';
} else if (c == '\n') {
c = '\r';
} else {
c = -1;
}
if (c >= 0) {
DWORD written;
WriteFile(m->hInput, &c, 1, &written, nullptr);
}
left = right;
} else {
right++;
}
}
}
-void Win32PtyProcess::start(QString const &cmdline)
+void Win32PtyProcess::start(QString const &cmdline, QVariant const &userdata)
{
if (isRunning()) return;
m->command = cmdline;
+ this->user_data = userdata;
QThread::start();
}
bool Win32PtyProcess::wait(unsigned long time)
{
return QThread::wait(time) && m->th_output_reader.wait(time);
}
void Win32PtyProcess::stop()
{
// 標準出力読み出しスレッドを強制終了しないとwinptyプロセスが終了してくれない
m->th_output_reader.terminate();
// プロセススレッド停止
requestInterruption();
wait();
}
const std::vector<char> *Win32PtyProcess::result() const
{
return &m->output_vector;
}
int Win32PtyProcess::getExitCode() const
{
return m->exit_code;
}
QString Win32PtyProcess::getMessage() const
{
QString s;
if (!m->output_vector.empty()) {
s = QString::fromUtf8(&m->output_vector[0], m->output_vector.size());
}
return s;
}
diff --git a/src/win32/Win32PtyProcess.h b/src/win32/Win32PtyProcess.h
index b2aecc1..a02976c 100644
--- a/src/win32/Win32PtyProcess.h
+++ b/src/win32/Win32PtyProcess.h
@@ -1,34 +1,34 @@
#ifndef WIN32PTYPROCESS_H
#define WIN32PTYPROCESS_H
#include <AbstractProcess.h>
#include <QString>
#include <QThread>
#include <vector>
class Win32PtyProcess : public AbstractPtyProcess, public QThread {
private:
struct Private;
Private *m;
static QString getProgram(QString const &cmdline);
protected:
void run();
public:
Win32PtyProcess();
~Win32PtyProcess();
bool isRunning() const;
int readOutput(char *dstptr, int maxlen);
void writeInput(char const *ptr, int len);
- void start(QString const &cmdline);
+ void start(QString const &cmdline, QVariant const &userdata);
bool wait(unsigned long time = ULONG_MAX);
void stop();
std::vector<char> const *result() const;
int getExitCode() const;
QString getMessage() const;
};
#endif // WIN32PTYPROCESS_H

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jun 16, 12:16 AM (2 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
71240
Default Alt Text
(485 KB)

Event Timeline