diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 4950d78..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pro.user* -src/translations/qterminal diff --git a/CHANGELOG b/CHANGELOG index 6298968..7a03b26 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,53 @@ -qterminal-0.7.1 / 2016-12-21 +qterminal-0.8.0 / 2017-10-21 ============================ + * Set version to 0.8.0 + * Update information on distribution package + * Added legacy font setting support + * Made font in settings file human readable + * Fix action inconsistency when switching tabs + * Lithuanian translation + * Added Lithuanian language + * Don't export github templates + * correct spelling mistake + * Adapt to QTermWidget API changes after DECSCUSR handling + * liblxqt dont make sense here + * Copied issue template + * Drops Qt5Core_VERSION_STRING + * Update qterminal_drop.desktop + * Update qterminal.desktop + * Make disabled actions consistent at all times (#331) + * DBus DropMode (#325) + * Change subterminal shortcuts to avoid breaking qtermwidget scrolling (#327) + * Update main.cpp (#322) + * Fix toggle menu action. + * Focus highlight (#266) + * Replace numbered terminals with directional navigation (#255) + * Fix '1 Terminal' preset (#324) + * Change "Clear Current Tab" into "Clear Active Terminal" (#268) + * Fix duplicated items, rework MainWindow memory-management (#313) + * Fixed comment - needless compiler warnings are annoying (#321) + * DBus integration (#307) + * Restore filter actions (#310) + * New features: trim \n from pasted strings, confirm multiline pastes (#309) + * Support custom QSS styles + * Call QApp destructor (#306) + * Fixes (#318) + * Refactor dangling actions, delete windows on close + * Adds superbuild support + * Improve translation (.ts) handling + * Removes Qt5X11Extras_DEFINITIONS + * Stops adding not exist entries to CMAKE_MODULE_PATH + * Use the LXQtCompilerSettings module + * Adapt to QTermWidget improved CMake + * Fix a copy/paste error from 4afcc4d0d0922f526f89aadf16ec0517f6b5267e + * Update dependencies and cleanup trailing spaces + +0.7.1 / 2016-12-21 +================== + + * Release 0.7.1: Update changelog * Bump patch version (#294) * Add common shortcuts for switching tabs (#275) * Fix tabstop order in properties dialog & add buddy relations for labels. (#290) diff --git a/CMakeLists.txt b/CMakeLists.txt index 496fc7f..b4de731 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,29 +4,16 @@ project(qterminal) include(GNUInstallDirs) -set(STR_VERSION "0.7.1") -set(LXQTBT_MINIMUM_VERSION "0.3.0") +# qterminal version +set(STR_VERSION "0.8.0") +set(LXQTBT_MINIMUM_VERSION "0.4.0") - -# additional cmake files -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") +option(UPDATE_TRANSLATIONS "Update source translation translations/*.ts files" OFF) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() -include(CheckCXXCompilerFlag) -CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) -CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) -if(COMPILER_SUPPORTS_CXX11) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -elseif(COMPILER_SUPPORTS_CXX0X) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") -else() - message(FATAL "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. C++11 support is required") -endif() - # we need qpa/qplatformnativeinterface.h for global shortcut find_package(Qt5Gui REQUIRED) find_package(Qt5Widgets REQUIRED) @@ -34,13 +21,13 @@ find_package(Qt5LinguistTools REQUIRED) if(APPLE) elseif(UNIX) find_package(Qt5X11Extras REQUIRED) + find_package(Qt5DBus) endif() find_package(QTermWidget5 REQUIRED) find_package(lxqt-build-tools ${LXQTBT_MINIMUM_VERSION} REQUIRED) include(LXQtTranslateTs) -message(STATUS "Qt version: ${Qt5Core_VERSION_STRING}") - -include(${QTERMWIDGET_USE_FILE}) +include(LXQtCompilerSettings NO_POLICY_SCOPE) +message(STATUS "Qt version: ${Qt5Core_VERSION}") # TODO remove Qxt message(STATUS "Using bundled Qxt...") @@ -56,8 +43,6 @@ elseif(UNIX) endif() add_definitions(-DSTR_VERSION=\"${STR_VERSION}\") -add_definitions(${Qt5X11Extras_DEFINITIONS}) - set(EXE_NAME qterminal) @@ -67,13 +52,16 @@ set(QTERM_SRC src/tabwidget.cpp src/termwidget.cpp src/termwidgetholder.cpp + src/terminalconfig.cpp src/properties.cpp src/propertiesdialog.cpp src/bookmarkswidget.cpp src/fontdialog.cpp + src/dbusaddressable.cpp ) set(QTERM_MOC_SRC + src/qterminalapp.h src/mainwindow.h src/tabwidget.h src/termwidget.h @@ -83,6 +71,17 @@ set(QTERM_MOC_SRC src/fontdialog.h ) + +if (Qt5DBus_FOUND) + add_definitions(-DHAVE_QDBUS) + QT5_ADD_DBUS_ADAPTOR(QTERM_SRC src/org.lxqt.QTerminal.Window.xml mainwindow.h MainWindow) + QT5_ADD_DBUS_ADAPTOR(QTERM_SRC src/org.lxqt.QTerminal.Tab.xml termwidgetholder.h TermWidgetHolder) + QT5_ADD_DBUS_ADAPTOR(QTERM_SRC src/org.lxqt.QTerminal.Terminal.xml termwidget.h TermWidget) + QT5_ADD_DBUS_ADAPTOR(QTERM_SRC src/org.lxqt.QTerminal.Process.xml qterminalapp.h QTerminalApp) + set(QTERM_MOC_SRC ${QTERM_MOC_SRC} src/dbusaddressable.h) + message(STATUS "Building with Qt5DBus support") +endif() + if(NOT QXT_FOUND) set(QTERM_SRC ${QTERM_SRC} src/third-party/qxtglobalshortcut.cpp) set(QTERM_MOC_SRC ${QTERM_MOC_SRC} src/third-party/qxtglobalshortcut.h) @@ -111,6 +110,12 @@ qt5_wrap_ui( QTERM_UI ${QTERM_UI_SRC} ) qt5_wrap_cpp( QTERM_MOC ${QTERM_MOC_SRC} ) qt5_add_resources( QTERM_RCC ${QTERM_RCC_SRC} ) lxqt_translate_ts(QTERM_QM + UPDATE_TRANSLATIONS + ${UPDATE_TRANSLATIONS} + SOURCES + ${QTERM_SRC} + ${QTERM_UI_SRC} + ${QTERM_MOC_SRC} TRANSLATION_DIR "src/translations" PULL_TRANSLATIONS ${PULL_TRANSLATIONS} @@ -123,10 +128,9 @@ lxqt_translate_ts(QTERM_QM ) include_directories( - "${CMAKE_SOURCE_DIR}" - "${CMAKE_SOURCE_DIR}/src" - "${CMAKE_BINARY_DIR}" - ${QTERMWIDGET_INCLUDE_DIRS} + "${PROJECT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_BINARY_DIR}" ${QXT_INCLUDE_DIRS} ) if(X11_FOUND) @@ -170,14 +174,18 @@ add_executable(${EXE_NAME} ${GUI_TYPE} ${QTERM_QM} ) target_link_libraries(${EXE_NAME} - ${QTERMWIDGET_QT_LIBRARIES} - ${QTERMWIDGET_LIBRARIES} + Qt5::Gui + qtermwidget5 util ) if(QXT_FOUND) target_link_libraries(${EXE_NAME} ${QXT_CORE_LIB} ${QXT_GUI_LIB}) endif() +if (Qt5DBus_FOUND) + target_link_libraries(${EXE_NAME} ${Qt5DBus_LIBRARIES}) +endif() + if(APPLE) target_link_libraries(${EXE_NAME} ${CARBON_LIBRARY}) elseif(UNIX) @@ -226,12 +234,3 @@ else() install(FILES ${QTERM_QM} DESTINATION ${CMAKE_INSTALL_PREFIX}/${EXE_NAME}.app/Contents/translations) endif() - - -# make lupdate -# it generates new translation files -add_custom_target(lupdate - ${QT_QMAKE_EXECUTABLE} -project -o "${CMAKE_CURRENT_BINARY_DIR}/qterminal.pro" - COMMAND ${QT_LUPDATE_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/qterminal.pro" - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" -) diff --git a/Doxyfile b/Doxyfile index 956c4e2..ed99210 100644 --- a/Doxyfile +++ b/Doxyfile @@ -282,7 +282,7 @@ TYPEDEF_HIDES_STRUCT = NO # causing a significant performance penality. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on -# a logarithmic scale so increasing the size by one will rougly double the +# a logarithmic scale so increasing the size by one will roughly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols diff --git a/README.md b/README.md index b7052e3..bcb713b 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,24 @@ ## Overview -QTerminal is a lightweight Qt terminal emulator based on [QTermWidget](https://github.com/lxde/qtermwidget). +QTerminal is a lightweight Qt terminal emulator based on [QTermWidget](https://github.com/lxde/qtermwidget). -It is maintained by the LXQt project but can be used independently from this desktop environment. The only bonds are [liblxqt](https://github.com/lxde/liblxqt) representing a build dependency and the localization files which were outsourced to LXQt repository [lxqt-l10n](https://github.com/lxde/lxqt-l10n). +It is maintained by the LXQt project but can be used independently from this desktop environment. The only bonds are [lxqt-build-tools](https://github.com/lxde/lxqt-build-tools) representing a build dependency and the localization files which were outsourced to LXQt repository [lxqt-l10n](https://github.com/lxde/lxqt-l10n). -This project is licensed under the terms of the [GPLv2](https://www.gnu.org/licenses/gpl-2.0.en.html) or any later version. See the LICENSE file for the full text of the license. +This project is licensed under the terms of the [GPLv2](https://www.gnu.org/licenses/gpl-2.0.en.html) or any later version. See the LICENSE file for the full text of the license. ## Installation ### Compiling sources -Runtime dependencies are qtx11extras ≥ 5.2 and [QTermWidget](https://github.com/lxde/qtermwidget). -In order to build CMake ≥ 3.0.2 and [liblxqt](https://github.com/lxde/liblxqt) are needed as well as optionally Git to pull latest VCS checkouts. The localization files were outsourced to repository [lxqt-l10n](https://github.com/lxde/lxqt-l10n) so the corresponding dependencies are needed, too. Please refer to this repository's `README.md` for further information. +Dependencies are qtx11extras ≥ 5.2 and [QTermWidget](https://github.com/lxde/qtermwidget). +In order to build CMake ≥ 3.0.2 and [lxqt-build-tools](https://github.com/lxde/lxqt-build-tools) are needed as well as optionally Git to pull latest VCS checkouts. The localization files were outsourced to repository [lxqt-l10n](https://github.com/lxde/lxqt-l10n) so the corresponding dependencies are needed, too. Please refer to this repository's `README.md` for further information. -Code configuration is handled by CMake. Building out of source is strongly recommended. CMake variable `CMAKE_INSTALL_PREFIX` will normally have to be set to `/usr`. +Code configuration is handled by CMake. Building out of source is strongly recommended. CMake variable `CMAKE_INSTALL_PREFIX` will normally have to be set to `/usr`. -To build run `make`, to install `make install` which accepts variable `DESTDIR` as usual. +To build run `make`, to install `make install` which accepts variable `DESTDIR` as usual. ### Binary packages -QTerminal is provided by all major Linux distributions like Arch Linux ([AUR](https://aur.archlinux.org) only so far), Debian (as of Debian stretch), Fedora and openSUSE (Tumbleweed only so far). +QTerminal is provided by all major Linux distributions like [Arch Linux](https://www.archlinux.org/packages/?q=qterminal), Debian (as of Debian stretch), Fedora and openSUSE. Just use the distributions' package managers to search for string `qterminal`. diff --git a/debian/changelog b/debian/changelog index 698f36b..d34ad72 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,16 @@ +qterminal (0.8.0-1) experimental; urgency=medium + + * Cherry-picking upstream version 0.8.0. + * Switched to experimental + * Bumped Standards to 4.1.1 + * Bumped libqtermwidget5-0-dev to >= 0.8.0 + * Added dependency libutf8proc-dev + * Bumped lxqt-build-tools to >= 0.4.0 + * Fixed copyright + * Dropped appdata-patch, applied upstream + + -- Alf Gaida Mon, 23 Oct 2017 01:47:22 +0200 + qterminal (0.7.1-3) unstable; urgency=medium * Bumped Standards to 4.1.0 - no changes needed diff --git a/debian/control b/debian/control index 01b76ac..8e792c6 100644 --- a/debian/control +++ b/debian/control @@ -7,15 +7,16 @@ Section: x11 Priority: optional Build-Depends: debhelper (>= 10), libkf5windowsystem-dev, - libqtermwidget5-0-dev, + libqtermwidget5-0-dev (>= 0.8.0), libqt5svg5-dev, libqt5x11extras5-dev, + libutf8proc-dev, libx11-dev, - lxqt-build-tools, + lxqt-build-tools (>= 0.4.0), qtbase5-private-dev -Standards-Version: 4.1.0 -Vcs-Browser: https://anonscm.debian.org/cgit/pkg-lxqt/qterminal.git/?h=debian/sid -Vcs-Git: https://anonscm.debian.org/git/pkg-lxqt/qterminal.git -b debian/sid +Standards-Version: 4.1.1 +Vcs-Browser: https://anonscm.debian.org/cgit/pkg-lxqt/qterminal.git/?h=debian/experimental +Vcs-Git: https://anonscm.debian.org/git/pkg-lxqt/qterminal.git -b debian/experimental Homepage: https://github.com/lxde/qterminal Package: qterminal diff --git a/debian/copyright b/debian/copyright index 099bd5d..62b6c74 100644 --- a/debian/copyright +++ b/debian/copyright @@ -4,6 +4,7 @@ Source: https://github.com/lxde/qterminal.git Files: * Copyright: 2010-2016 Petr Vanek + 2016-2017 LXQt Team License: GPL-2+ Files: src/config.h diff --git a/debian/gbp.conf b/debian/gbp.conf index 7a9f00a..ffe50a8 100644 --- a/debian/gbp.conf +++ b/debian/gbp.conf @@ -1,5 +1,5 @@ [DEFAULT] -debian-branch = debian/sid +debian-branch = debian/experimental upstream-branch = upstream/latest pristine-tar = True diff --git a/debian/patches/fix-appdata-format.patch b/debian/patches/fix-appdata-format.patch deleted file mode 100644 index edfe3fa..0000000 --- a/debian/patches/fix-appdata-format.patch +++ /dev/null @@ -1,31 +0,0 @@ -Description: Fixes appdata format - * application -> component -Author: Alf Gaida -Last-Update: 2017-08-13 - ---- a/qterminal.appdata.xml -+++ b/qterminal.appdata.xml -@@ -1,5 +1,5 @@ - -- -+ - qterminal.desktop - CC0-1.0 - GPL-2 -@@ -40,4 +40,4 @@ - - - https://github.com/lxde/qterminal -- -+ ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -197,7 +197,7 @@ - - install(FILES - qterminal.appdata.xml -- DESTINATION "${CMAKE_INSTALL_DATADIR}/appdata" -+ DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo" - ) - - if(NOT APPLEBUNDLE) diff --git a/debian/patches/series b/debian/patches/series deleted file mode 100644 index a61622c..0000000 --- a/debian/patches/series +++ /dev/null @@ -1 +0,0 @@ -fix-appdata-format.patch diff --git a/qterminal.desktop b/qterminal.desktop index fa20c01..ca5ce8a 100644 --- a/qterminal.desktop +++ b/qterminal.desktop @@ -7,6 +7,8 @@ Comment=Terminal emulator Comment[de]=Befehlszeile verwenden Comment[el]=Προσομοιωτής τερματικού Comment[fr]=Terminal +Comment[lt]=Terminalo emuliatorius +Comment[pl]=Emulator terminala Comment[pt]=Emulador de terminal Comment[pt_BR]=Emulador de terminal Comment[ru_RU]=Эмулятор терминала @@ -40,10 +42,12 @@ Name[it]=Terminale a discesa Name[ja]=ドロップダウン式ターミナル Name[km]=ស្ថានីយ​ទម្លាក់​ចុះ Name[ko]=위에서 내려오는 터미널 +Name[lt]=Išskleidžiamasis terminalas Name[nb]=Nedtrekksterminal Name[nds]=Utklapp-Konsool Name[nl]=Uitvouwbare terminalemulator Name[pa]=ਲਟਕਦਾ ਟਰਮੀਨਲ +Name[pl]=Rozwijany emulator terminala Name[pt]=Terminal suspenso Name[pt_BR]=Terminal suspenso Name[ro]=Terminal derulant diff --git a/qterminal_drop.desktop b/qterminal_drop.desktop index d40d609..5654540 100644 --- a/qterminal_drop.desktop +++ b/qterminal_drop.desktop @@ -8,6 +8,8 @@ Icon=utilities-terminal Name=QTerminal drop down Name[de]=QTerminal herabhängend Name[el]=QTerminal αναπτυσσόμενο +Name[lt]=QTerminal išskleidžiamasis +Name[pl]=QTerminal (tryb rozwijany) Name[pt]=QTerminal suspenso Name[pt_BR]=QTerminal suspenso Name[ja]=QTerminal ドロップダウン @@ -30,10 +32,12 @@ GenericName[it]=Terminale a discesa GenericName[ja]=ドロップダウン式ターミナル GenericName[km]=ស្ថានីយ​ទម្លាក់​ចុះ GenericName[ko]=위에서 내려오는 터미널 +GenericName[lt]=Išskleidžiamasis terminalas GenericName[nb]=Nedtrekksterminal GenericName[nds]=Utklapp-Konsool GenericName[nl]=Uitvouwbare terminalemulator GenericName[pa]=ਲਟਕਦਾ ਟਰਮੀਨਲ +GenericName[pl]=Rozwijany emulator terminala GenericName[pt]=Terminal suspenso GenericName[pt_BR]=Terminal suspenso GenericName[ro]=Terminal derulant @@ -50,6 +54,7 @@ GenericName[zh_TW]=下拉式終端機 Comment=A drop-down terminal emulator. Comment[de]=Ein Ausklapp-Terminalemulator. Comment[el]=Ένας αναπτυσσόμενος προσομοιωτής τερματικού. +Comment[lt]=Išskleidžiamasis terminalo emuliatorius. Comment[pt]=Um emulador de terminal suspenso. Comment[pt_BR]=Um emulador de terminal suspenso. Comment[ru]=Вападающий эмулятор терминала. diff --git a/src/bookmarkswidget.cpp b/src/bookmarkswidget.cpp index 432dc22..c7f46d1 100644 --- a/src/bookmarkswidget.cpp +++ b/src/bookmarkswidget.cpp @@ -140,7 +140,7 @@ public: // system env - include dirs in the tree QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - foreach (QString i, env.keys()) + foreach (const QString &i, env.keys()) { path = env.value(i); if (!d.exists(path) || !QFileInfo(path).isDir()) @@ -217,7 +217,7 @@ public: break; } case QXmlStreamReader::Invalid: - qDebug() << "XML error: " << xml.errorString().data() + qDebug() << "XML error: " << xml.errorString().constData() << xml.lineNumber() << xml.columnNumber(); m_map.clear(); return; diff --git a/src/config.h b/src/config.h index 4219d3b..49a12a1 100644 --- a/src/config.h +++ b/src/config.h @@ -37,8 +37,10 @@ #define SPLIT_VERTICAL "Split Terminal Vertically" #define SUB_COLLAPSE "Collapse Subterminal" -#define SUB_NEXT "Next Subterminal" -#define SUB_PREV "Previous Subterminal" +#define SUB_LEFT "Left Subterminal" +#define SUB_RIGHT "Right Subterminal" +#define SUB_TOP "Top Subterminal" +#define SUB_BOTTOM "Bottom Subterminal" #define MOVE_LEFT "Move Tab Left" #define MOVE_RIGHT "Move Tab Right" @@ -69,10 +71,13 @@ // ACTIONS #define CLEAR_TERMINAL_SHORTCUT "Ctrl+Shift+X" -#define TAB_PREV_SHORTCUT "Shift+Left|Ctrl+PgUp|Ctrl+Shift+Tab" -#define TAB_NEXT_SHORTCUT "Shift+Right|Ctrl+PgDown|Ctrl+Tab" -#define SUB_PREV_SHORTCUT "Shift+Down" -#define SUB_NEXT_SHORTCUT "Shift+Up" + +#define TAB_PREV_SHORTCUT "Ctrl+PgUp|Ctrl+Shift+Tab" +#define TAB_NEXT_SHORTCUT "Ctrl+PgDown|Ctrl+Tab" +#define SUB_BOTTOM_SHORTCUT "Alt+Down" +#define SUB_TOP_SHORTCUT "Alt+Up" +#define SUB_LEFT_SHORTCUT "Alt+Left" +#define SUB_RIGHT_SHORTCUT "Alt+Right" #ifdef Q_WS_MAC // It's tricky - Ctrl is "command" key on mac's keyboards diff --git a/src/dbusaddressable.cpp b/src/dbusaddressable.cpp new file mode 100644 index 0000000..a822f96 --- /dev/null +++ b/src/dbusaddressable.cpp @@ -0,0 +1,25 @@ + + +#include "dbusaddressable.h" + +#ifdef HAVE_QDBUS +Q_DECLARE_METATYPE(QList) + +QString DBusAddressable::getDbusPathString() +{ + return m_path; +} + +QDBusObjectPath DBusAddressable::getDbusPath() +{ + return QDBusObjectPath(m_path); +} +#endif + +DBusAddressable::DBusAddressable(QString prefix) +{ + #ifdef HAVE_QDBUS + QString uuidString = QUuid::createUuid().toString(); + m_path = prefix + "/" + uuidString.replace(QRegExp("[\\{\\}\\-]"), ""); + #endif +} diff --git a/src/dbusaddressable.h b/src/dbusaddressable.h new file mode 100644 index 0000000..1059bed --- /dev/null +++ b/src/dbusaddressable.h @@ -0,0 +1,34 @@ +#ifndef DBUSADDRESSABLE_H +#define DBUSADDRESSABLE_H + +#include +#ifdef HAVE_QDBUS +#include +#include +#endif + +class DBusAddressable +{ + #ifdef HAVE_QDBUS + private: + QString m_path; + #endif + public: + #ifdef HAVE_QDBUS + QDBusObjectPath getDbusPath(); + QString getDbusPathString(); + #endif + DBusAddressable(QString prefix); +}; + +#ifdef HAVE_QDBUS +template void registerAdapter(WClass *obj) +{ + new AClass(obj); + QString path = dynamic_cast(obj)->getDbusPathString(); + QDBusConnection::sessionBus().registerObject(path, obj); +} +#endif + + +#endif \ No newline at end of file diff --git a/src/forms/propertiesdialog.ui b/src/forms/propertiesdialog.ui index ff26b76..759549f 100644 --- a/src/forms/propertiesdialog.ui +++ b/src/forms/propertiesdialog.ui @@ -423,6 +423,20 @@ + + + + Confirm multiline paste + + + + + + + Trim trailing newlines in pasted text + + + @@ -436,7 +450,7 @@ - + Qt::Vertical @@ -449,28 +463,28 @@ - + Open new terminals in current working directory - + Save Size when closing - + Save Position when closing - + Ask for confirmation when closing diff --git a/src/forms/qterminal.ui b/src/forms/qterminal.ui index c921f50..3e06edb 100644 --- a/src/forms/qterminal.ui +++ b/src/forms/qterminal.ui @@ -111,21 +111,6 @@ About &Qt... - - - &Preferences... - - - - - - - - - - &Quit - - diff --git a/src/main.cpp b/src/main.cpp index 275d919..42cf62e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,11 +19,20 @@ #include #include +#include #include #include #include +#ifdef HAVE_QDBUS + #include + #include + #include "processadaptor.h" +#endif -#include "mainwindow.h" + +#include "mainwindow.h" +#include "qterminalapp.h" +#include "terminalconfig.h" #define out @@ -39,6 +48,8 @@ const struct option long_options[] = { {NULL, 0, NULL, 0} }; +QTerminalApp * QTerminalApp::m_instance = NULL; + void print_usage_and_exit(int code) { printf("QTerminal %s\n", STR_VERSION); @@ -111,16 +122,35 @@ int main(int argc, char *argv[]) // Warning: do not change settings format. It can screw bookmarks later. QSettings::setDefaultFormat(QSettings::IniFormat); - QApplication app(argc, argv); + QTerminalApp *app = QTerminalApp::Instance(argc, argv); + #ifdef HAVE_QDBUS + app->registerOnDbus(); + #endif + QString workdir, shell_command; bool dropMode; parse_args(argc, argv, workdir, shell_command, dropMode); if (workdir.isEmpty()) workdir = QDir::currentPath(); + app->setWorkingDirectory(workdir); + + const QSettings settings; + const QFileInfo customStyle = QFileInfo( + QFileInfo(settings.fileName()).canonicalPath() + + "/style.qss" + ); + if (customStyle.isFile() && customStyle.isReadable()) + { + QFile style(customStyle.canonicalFilePath()); + style.open(QFile::ReadOnly); + QString styleString = QLatin1String(style.readAll()); + app->setStyleSheet(styleString); + } // icons /* setup our custom icon theme if there is no system theme (OS X, Windows) */ + QCoreApplication::instance()->setAttribute(Qt::AA_UseHighDpiPixmaps); //Fix for High-DPI systems if (QIcon::themeName().isEmpty()) QIcon::setThemeName("QTerminal"); @@ -135,25 +165,151 @@ int main(int argc, char *argv[]) qDebug() << "APPLE_BUNDLE: Loading translator file" << fname << "from dir" << QApplication::applicationDirPath()+"../translations"; qDebug() << "load success:" << translator.load(fname, QApplication::applicationDirPath()+"../translations", "_"); #endif - app.installTranslator(&translator); + app->installTranslator(&translator); - MainWindow *window; + TerminalConfig initConfig = TerminalConfig(workdir, shell_command); + app->newWindow(dropMode, initConfig); + + int ret = app->exec(); + delete Properties::Instance(); + app->cleanup(); + + return ret; +} + +MainWindow *QTerminalApp::newWindow(bool dropMode, TerminalConfig &cfg) +{ + MainWindow *window = NULL; if (dropMode) { QWidget *hiddenPreviewParent = new QWidget(0, Qt::Tool); - window = new MainWindow(workdir, shell_command, dropMode, hiddenPreviewParent); + window = new MainWindow(cfg, dropMode, hiddenPreviewParent); if (Properties::Instance()->dropShowOnStart) window->show(); } else { - window = new MainWindow(workdir, shell_command, dropMode); + window = new MainWindow(cfg, dropMode); window->show(); } - - int ret = app.exec(); - delete Properties::Instance(); - delete window; - - return ret; + return window; } + +QTerminalApp *QTerminalApp::Instance() +{ + assert(m_instance != NULL); + return m_instance; +} + +QTerminalApp *QTerminalApp::Instance(int &argc, char **argv) +{ + assert(m_instance == NULL); + m_instance = new QTerminalApp(argc, argv); + return m_instance; +} + +QTerminalApp::QTerminalApp(int &argc, char **argv) + :QApplication(argc, argv) +{ +} + +QString &QTerminalApp::getWorkingDirectory() +{ + return m_workDir; +} + +void QTerminalApp::setWorkingDirectory(const QString &wd) +{ + m_workDir = wd; +} + +void QTerminalApp::cleanup() { + delete m_instance; + m_instance = NULL; +} + + +void QTerminalApp::addWindow(MainWindow *window) +{ + m_windowList.append(window); +} + +void QTerminalApp::removeWindow(MainWindow *window) +{ + m_windowList.removeOne(window); +} + +QList QTerminalApp::getWindowList() +{ + return m_windowList; +} + +#ifdef HAVE_QDBUS +void QTerminalApp::registerOnDbus() +{ + if (!QDBusConnection::sessionBus().isConnected()) + { + fprintf(stderr, "Cannot connect to the D-Bus session bus.\n" + "To start it, run:\n" + "\teval `dbus-launch --auto-syntax`\n"); + return; + } + QString serviceName = QStringLiteral("org.lxqt.QTerminal-%1").arg(getpid()); + if (!QDBusConnection::sessionBus().registerService(serviceName)) + { + fprintf(stderr, "%s\n", qPrintable(QDBusConnection::sessionBus().lastError().message())); + return; + } + new ProcessAdaptor(this); + QDBusConnection::sessionBus().registerObject("/", this); +} + +QList QTerminalApp::getWindows() +{ + QList windows; + foreach (MainWindow *wnd, m_windowList) + { + windows.push_back(wnd->getDbusPath()); + } + return windows; +} + +QDBusObjectPath QTerminalApp::newWindow(const QHash &termArgs) +{ + TerminalConfig cfg = TerminalConfig::fromDbus(termArgs); + MainWindow *wnd = newWindow(false, cfg); + assert(wnd != NULL); + return wnd->getDbusPath(); +} + +QDBusObjectPath QTerminalApp::getActiveWindow() +{ + QWidget *aw = activeWindow(); + if (aw == NULL) + return QDBusObjectPath("/"); + return qobject_cast(aw)->getDbusPath(); +} + +bool QTerminalApp::isDropMode() { + if (m_windowList.count() == 0) { + return false; + } + MainWindow *wnd = m_windowList.at(0); + return wnd->dropMode(); +} + +bool QTerminalApp::toggleDropdown() { + if (m_windowList.count() == 0) { + return false; + } + MainWindow *wnd = m_windowList.at(0); + if (!wnd->dropMode()) { + return false; + } + wnd->showHide(); + return true; +} + + +#endif + diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c6a6617..f1790a2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -22,6 +22,12 @@ #include #include +#ifdef HAVE_QDBUS + #include + #include "windowadaptor.h" +#endif + +#include "terminalconfig.h" #include "mainwindow.h" #include "tabwidget.h" #include "termwidgetholder.h" @@ -29,7 +35,8 @@ #include "properties.h" #include "propertiesdialog.h" #include "bookmarkswidget.h" - +#include "qterminalapp.h" +#include "dbusaddressable.h" typedef std::function checkfn; Q_DECLARE_METATYPE(checkfn) @@ -37,18 +44,30 @@ Q_DECLARE_METATYPE(checkfn) // TODO/FXIME: probably remove. QSS makes it unusable on mac... #define QSS_DROP "MainWindow {border: 1px solid rgba(0, 0, 0, 50%);}\n" -MainWindow::MainWindow(const QString& work_dir, - const QString& command, +MainWindow::MainWindow(TerminalConfig &cfg, bool dropMode, QWidget * parent, Qt::WindowFlags f) : QMainWindow(parent,f), - m_initShell(command), - m_initWorkDir(work_dir), + DBusAddressable("/windows"), + tabPosition(NULL), + scrollBarPosition(NULL), + keyboardCursorShape(NULL), + tabPosMenu(NULL), + scrollPosMenu(NULL), + keyboardCursorShapeMenu(NULL), + settingOwner(NULL), + presetsMenu(NULL), + m_config(cfg), m_dropLockButton(0), m_dropMode(dropMode) { +#ifdef HAVE_QDBUS + registerAdapter(this); +#endif + QTerminalApp::Instance()->addWindow(this); setAttribute(Qt::WA_TranslucentBackground); + setAttribute(Qt::WA_DeleteOnClose); setupUi(this); Properties::Instance()->migrate_settings(); @@ -88,26 +107,39 @@ MainWindow::MainWindow(const QString& work_dir, consoleTabulator->setAutoFillBackground(true); connect(consoleTabulator, SIGNAL(closeTabNotification()), SLOT(close())); - consoleTabulator->setWorkDirectory(work_dir); consoleTabulator->setTabPosition((QTabWidget::TabPosition)Properties::Instance()->tabsPos); //consoleTabulator->setShellProgram(command); - setup_FileMenu_Actions(); - setup_ActionsMenu_Actions(); - setup_ViewMenu_Actions(); + // apply props + propertiesChanged(); + setupCustomDirs(); connect(consoleTabulator, &TabWidget::currentTitleChanged, this, &MainWindow::onCurrentTitleChanged); - connect(menu_Actions, SIGNAL(aboutToShow()), this, SLOT(aboutToShowActionsMenu())); + connect(menu_Actions, SIGNAL(aboutToShow()), this, SLOT(updateDisabledActions())); /* The tab should be added after all changes are made to the main window; otherwise, the initial prompt might get jumbled because of changes in internal geometry. */ - consoleTabulator->addNewTab(command); + consoleTabulator->addNewTab(m_config); +} + +void MainWindow::rebuildActions() +{ + // Delete all setting-related QObjects + delete settingOwner; + settingOwner = new QWidget(this); + settingOwner->setGeometry(0,0,0,0); + + // Then create them again + setup_FileMenu_Actions(); + setup_ActionsMenu_Actions(); + setup_ViewMenu_Actions(); } MainWindow::~MainWindow() { + QTerminalApp::Instance()->removeWindow(this); } void MainWindow::enableDropMode() @@ -145,22 +177,22 @@ void MainWindow::setup_Action(const char *name, QAction *action, const char *def QList shortcuts; - Properties::Instance()->actions[name] = action; + actions[name] = action; foreach (const QString &sequenceString, settings.value(name, defaultShortcut).toString().split('|')) shortcuts.append(QKeySequence::fromString(sequenceString)); - Properties::Instance()->actions[name]->setShortcuts(shortcuts); + actions[name]->setShortcuts(shortcuts); if (receiver) { - connect(Properties::Instance()->actions[name], SIGNAL(triggered(bool)), receiver, slot); - addAction(Properties::Instance()->actions[name]); + connect(actions[name], SIGNAL(triggered(bool)), receiver, slot); + addAction(actions[name]); } if (menu) - menu->addAction(Properties::Instance()->actions[name]); + menu->addAction(actions[name]); if (!data.isNull()) - Properties::Instance()->actions[name]->setData(data); + actions[name]->setData(data); } void MainWindow::setup_ActionsMenu_Actions() @@ -170,68 +202,77 @@ void MainWindow::setup_ActionsMenu_Actions() const checkfn checkTabs = &MainWindow::hasMultipleTabs; const checkfn checkSubterminals = &MainWindow::hasMultipleSubterminals; - setup_Action(CLEAR_TERMINAL, new QAction(QIcon::fromTheme("edit-clear"), tr("&Clear Current Tab"), this), + menu_Actions->clear(); + + setup_Action(CLEAR_TERMINAL, new QAction(QIcon::fromTheme("edit-clear"), tr("&Clear Active Terminal"), settingOwner), CLEAR_TERMINAL_SHORTCUT, consoleTabulator, SLOT(clearActiveTerminal()), menu_Actions); menu_Actions->addSeparator(); data.setValue(checkTabs); - setup_Action(TAB_NEXT, new QAction(QIcon::fromTheme("go-next"), tr("&Next Tab"), this), + setup_Action(TAB_NEXT, new QAction(QIcon::fromTheme("go-next"), tr("&Next Tab"), settingOwner), TAB_NEXT_SHORTCUT, consoleTabulator, SLOT(switchToRight()), menu_Actions, data); - setup_Action(TAB_PREV, new QAction(QIcon::fromTheme("go-previous"), tr("&Previous Tab"), this), + setup_Action(TAB_PREV, new QAction(QIcon::fromTheme("go-previous"), tr("&Previous Tab"), settingOwner), TAB_PREV_SHORTCUT, consoleTabulator, SLOT(switchToLeft()), menu_Actions, data); - setup_Action(MOVE_LEFT, new QAction(tr("Move Tab &Left"), this), + setup_Action(MOVE_LEFT, new QAction(tr("Move Tab &Left"), settingOwner), MOVE_LEFT_SHORTCUT, consoleTabulator, SLOT(moveLeft()), menu_Actions, data); - setup_Action(MOVE_RIGHT, new QAction(tr("Move Tab &Right"), this), + setup_Action(MOVE_RIGHT, new QAction(tr("Move Tab &Right"), settingOwner), MOVE_RIGHT_SHORTCUT, consoleTabulator, SLOT(moveRight()), menu_Actions, data); menu_Actions->addSeparator(); - setup_Action(SPLIT_HORIZONTAL, new QAction(tr("Split Terminal &Horizontally"), this), + setup_Action(SPLIT_HORIZONTAL, new QAction(tr("Split Terminal &Horizontally"), settingOwner), NULL, consoleTabulator, SLOT(splitHorizontally()), menu_Actions); - setup_Action(SPLIT_VERTICAL, new QAction(tr("Split Terminal &Vertically"), this), + setup_Action(SPLIT_VERTICAL, new QAction(tr("Split Terminal &Vertically"), settingOwner), NULL, consoleTabulator, SLOT(splitVertically()), menu_Actions); data.setValue(checkSubterminals); - setup_Action(SUB_COLLAPSE, new QAction(tr("&Collapse Subterminal"), this), + setup_Action(SUB_COLLAPSE, new QAction(tr("&Collapse Subterminal"), settingOwner), NULL, consoleTabulator, SLOT(splitCollapse()), menu_Actions, data); - setup_Action(SUB_NEXT, new QAction(QIcon::fromTheme("go-up"), tr("N&ext Subterminal"), this), - SUB_NEXT_SHORTCUT, consoleTabulator, SLOT(switchNextSubterminal()), menu_Actions, data); + setup_Action(SUB_TOP, new QAction(QIcon::fromTheme("go-up"), tr("&Top Subterminal"), settingOwner), + SUB_TOP_SHORTCUT, consoleTabulator, SLOT(switchTopSubterminal()), menu_Actions, data); + + setup_Action(SUB_BOTTOM, new QAction(QIcon::fromTheme("go-down"), tr("&Bottom Subterminal"), settingOwner), + SUB_BOTTOM_SHORTCUT, consoleTabulator, SLOT(switchBottomSubterminal()), menu_Actions, data); + + setup_Action(SUB_LEFT, new QAction(QIcon::fromTheme("go-previous"), tr("L&eft Subterminal"), settingOwner), + SUB_LEFT_SHORTCUT, consoleTabulator, SLOT(switchLeftSubterminal()), menu_Actions, data); + + setup_Action(SUB_RIGHT, new QAction(QIcon::fromTheme("go-next"), tr("R&ight Subterminal"), settingOwner), + SUB_RIGHT_SHORTCUT, consoleTabulator, SLOT(switchRightSubterminal()), menu_Actions, data); - setup_Action(SUB_PREV, new QAction(QIcon::fromTheme("go-down"), tr("P&revious Subterminal"), this), - SUB_PREV_SHORTCUT, consoleTabulator, SLOT(switchPrevSubterminal()), menu_Actions, data); menu_Actions->addSeparator(); // Copy and Paste are only added to the table for the sake of bindings at the moment; there is no Edit menu, only a context menu. - setup_Action(COPY_SELECTION, new QAction(QIcon::fromTheme("edit-copy"), tr("Copy &Selection"), this), + setup_Action(COPY_SELECTION, new QAction(QIcon::fromTheme("edit-copy"), tr("Copy &Selection"), settingOwner), COPY_SELECTION_SHORTCUT, consoleTabulator, SLOT(copySelection()), menu_Edit); - setup_Action(PASTE_CLIPBOARD, new QAction(QIcon::fromTheme("edit-paste"), tr("Paste Clip&board"), this), + setup_Action(PASTE_CLIPBOARD, new QAction(QIcon::fromTheme("edit-paste"), tr("Paste Clip&board"), settingOwner), PASTE_CLIPBOARD_SHORTCUT, consoleTabulator, SLOT(pasteClipboard()), menu_Edit); - setup_Action(PASTE_SELECTION, new QAction(QIcon::fromTheme("edit-paste"), tr("Paste S&election"), this), + setup_Action(PASTE_SELECTION, new QAction(QIcon::fromTheme("edit-paste"), tr("Paste S&election"), settingOwner), PASTE_SELECTION_SHORTCUT, consoleTabulator, SLOT(pasteSelection()), menu_Edit); - setup_Action(ZOOM_IN, new QAction(QIcon::fromTheme("zoom-in"), tr("Zoom &in"), this), + setup_Action(ZOOM_IN, new QAction(QIcon::fromTheme("zoom-in"), tr("Zoom &in"), settingOwner), ZOOM_IN_SHORTCUT, consoleTabulator, SLOT(zoomIn()), menu_Edit); - setup_Action(ZOOM_OUT, new QAction(QIcon::fromTheme("zoom-out"), tr("Zoom &out"), this), + setup_Action(ZOOM_OUT, new QAction(QIcon::fromTheme("zoom-out"), tr("Zoom &out"), settingOwner), ZOOM_OUT_SHORTCUT, consoleTabulator, SLOT(zoomOut()), menu_Edit); - setup_Action(ZOOM_RESET, new QAction(QIcon::fromTheme("zoom-original"), tr("Zoom rese&t"), this), + setup_Action(ZOOM_RESET, new QAction(QIcon::fromTheme("zoom-original"), tr("Zoom rese&t"), settingOwner), ZOOM_RESET_SHORTCUT, consoleTabulator, SLOT(zoomReset()), menu_Edit); menu_Actions->addSeparator(); - setup_Action(FIND, new QAction(QIcon::fromTheme("edit-find"), tr("&Find..."), this), + setup_Action(FIND, new QAction(QIcon::fromTheme("edit-find"), tr("&Find..."), settingOwner), FIND_SHORTCUT, this, SLOT(find()), menu_Actions); #if 0 @@ -252,63 +293,66 @@ void MainWindow::setup_ActionsMenu_Actions() connect(act, SIGNAL(triggered()), consoleTabulator, SLOT(loadSession())); #endif - setup_Action(TOGGLE_MENU, new QAction(tr("&Toggle Menu"), this), - TOGGLE_MENU_SHORTCUT, this, SLOT(find())); + setup_Action(TOGGLE_MENU, new QAction(tr("&Toggle Menu"), settingOwner), + TOGGLE_MENU_SHORTCUT, this, SLOT(toggleMenu())); // this is correct - add action to main window - not to menu to keep toggle working // Add global rename current session shortcut - setup_Action(RENAME_SESSION, new QAction(tr("Rename session"), this), + setup_Action(RENAME_SESSION, new QAction(tr("Rename session"), settingOwner), RENAME_SESSION_SHORTCUT, consoleTabulator, SLOT(renameCurrentSession())); // this is correct - add action to main window - not to menu - // apply props - propertiesChanged(); } void MainWindow::setup_FileMenu_Actions() { - setup_Action(ADD_TAB, new QAction(QIcon::fromTheme("list-add"), tr("&New Tab"), this), + menu_File->clear(); + setup_Action(ADD_TAB, new QAction(QIcon::fromTheme("list-add"), tr("&New Tab"), settingOwner), ADD_TAB_SHORTCUT, this, SLOT(addNewTab()), menu_File); - QMenu *presetsMenu = new QMenu(tr("New Tab From &Preset"), this); - presetsMenu->addAction(QIcon(), tr("1 &Terminal"), - consoleTabulator, SLOT(addNewTab())); - presetsMenu->addAction(QIcon(), tr("2 &Horizontal Terminals"), - consoleTabulator, SLOT(preset2Horizontal())); - presetsMenu->addAction(QIcon(), tr("2 &Vertical Terminals"), - consoleTabulator, SLOT(preset2Vertical())); - presetsMenu->addAction(QIcon(), tr("4 Terminal&s"), - consoleTabulator, SLOT(preset4Terminals())); + if (presetsMenu == NULL) { + presetsMenu = new QMenu(tr("New Tab From &Preset"), this); + presetsMenu->addAction(QIcon(), tr("1 &Terminal"), + this, SLOT(addNewTab())); + presetsMenu->addAction(QIcon(), tr("2 &Horizontal Terminals"), + consoleTabulator, SLOT(preset2Horizontal())); + presetsMenu->addAction(QIcon(), tr("2 &Vertical Terminals"), + consoleTabulator, SLOT(preset2Vertical())); + presetsMenu->addAction(QIcon(), tr("4 Terminal&s"), + consoleTabulator, SLOT(preset4Terminals())); + } + menu_File->addMenu(presetsMenu); - setup_Action(CLOSE_TAB, new QAction(QIcon::fromTheme("list-remove"), tr("&Close Tab"), this), + setup_Action(CLOSE_TAB, new QAction(QIcon::fromTheme("list-remove"), tr("&Close Tab"), settingOwner), CLOSE_TAB_SHORTCUT, consoleTabulator, SLOT(removeCurrentTab()), menu_File); - setup_Action(NEW_WINDOW, new QAction(QIcon::fromTheme("window-new"), tr("&New Window"), this), + setup_Action(NEW_WINDOW, new QAction(QIcon::fromTheme("window-new"), tr("&New Window"), settingOwner), NEW_WINDOW_SHORTCUT, this, SLOT(newTerminalWindow()), menu_File); menu_File->addSeparator(); - setup_Action(PREFERENCES, actProperties, "", this, SLOT(actProperties_triggered()), menu_File); + setup_Action(PREFERENCES, new QAction(tr("&Preferences..."), settingOwner), "", this, SLOT(actProperties_triggered()), menu_File); menu_File->addSeparator(); - setup_Action(QUIT, actQuit, "", this, SLOT(close()), menu_File); + setup_Action(QUIT, new QAction(QIcon::fromTheme("application-exit"), tr("&Quit"), settingOwner), "", this, SLOT(close()), menu_File); } void MainWindow::setup_ViewMenu_Actions() { - QAction *hideBordersAction = new QAction(tr("&Hide Window Borders"), this); + menu_Window->clear(); + QAction *hideBordersAction = new QAction(tr("&Hide Window Borders"), settingOwner); hideBordersAction->setCheckable(true); hideBordersAction->setVisible(!m_dropMode); setup_Action(HIDE_WINDOW_BORDERS, hideBordersAction, NULL, this, SLOT(toggleBorderless()), menu_Window); //Properties::Instance()->actions[HIDE_WINDOW_BORDERS]->setObjectName("toggle_Borderless"); // TODO/FIXME: it's broken somehow. When I call toggleBorderless() here the non-responsive window appear -// Properties::Instance()->actions[HIDE_WINDOW_BORDERS]->setChecked(Properties::Instance()->borderless); +// actions[HIDE_WINDOW_BORDERS]->setChecked(Properties::Instance()->borderless); // if (Properties::Instance()->borderless) // toggleBorderless(); - QAction *showTabBarAction = new QAction(tr("&Show Tab Bar"), this); + QAction *showTabBarAction = new QAction(tr("&Show Tab Bar"), settingOwner); //toggleTabbar->setObjectName("toggle_TabBar"); showTabBarAction->setCheckable(true); showTabBarAction->setChecked(!Properties::Instance()->tabBarless); @@ -316,30 +360,33 @@ void MainWindow::setup_ViewMenu_Actions() NULL, this, SLOT(toggleTabBar()), menu_Window); toggleTabBar(); - QAction *toggleFullscreen = new QAction(tr("Fullscreen"), this); + QAction *toggleFullscreen = new QAction(tr("Fullscreen"), settingOwner); toggleFullscreen->setCheckable(true); toggleFullscreen->setChecked(false); setup_Action(FULLSCREEN, toggleFullscreen, FULLSCREEN_SHORTCUT, this, SLOT(showFullscreen(bool)), menu_Window); - setup_Action(TOGGLE_BOOKMARKS, m_bookmarksDock->toggleViewAction(), + setup_Action(TOGGLE_BOOKMARKS, new QAction(tr("Toggle Bookmarks"), settingOwner), TOGGLE_BOOKMARKS_SHORTCUT, NULL, NULL, menu_Window); menu_Window->addSeparator(); /* tabs position */ - tabPosition = new QActionGroup(this); - QAction *tabBottom = new QAction(tr("&Bottom"), this); - QAction *tabTop = new QAction(tr("&Top"), this); - QAction *tabRight = new QAction(tr("&Right"), this); - QAction *tabLeft = new QAction(tr("&Left"), this); - tabPosition->addAction(tabTop); - tabPosition->addAction(tabBottom); - tabPosition->addAction(tabLeft); - tabPosition->addAction(tabRight); + if (tabPosition == NULL) { + tabPosition = new QActionGroup(this); + QAction *tabBottom = new QAction(tr("&Bottom"), this); + QAction *tabTop = new QAction(tr("&Top"), this); + QAction *tabRight = new QAction(tr("&Right"), this); + QAction *tabLeft = new QAction(tr("&Left"), this); + tabPosition->addAction(tabTop); + tabPosition->addAction(tabBottom); + tabPosition->addAction(tabLeft); + tabPosition->addAction(tabRight); + + for(int i = 0; i < tabPosition->actions().size(); ++i) + tabPosition->actions().at(i)->setCheckable(true); + } - for(int i = 0; i < tabPosition->actions().size(); ++i) - tabPosition->actions().at(i)->setCheckable(true); if( tabPosition->actions().count() > Properties::Instance()->tabsPos ) tabPosition->actions().at(Properties::Instance()->tabsPos)->setChecked(true); @@ -347,96 +394,84 @@ void MainWindow::setup_ViewMenu_Actions() connect(tabPosition, SIGNAL(triggered(QAction *)), consoleTabulator, SLOT(changeTabPosition(QAction *)) ); - tabPosMenu = new QMenu(tr("&Tabs Layout"), menu_Window); - tabPosMenu->setObjectName("tabPosMenu"); + if (tabPosMenu == NULL) { + tabPosMenu = new QMenu(tr("&Tabs Layout"), menu_Window); + tabPosMenu->setObjectName("tabPosMenu"); - for(int i=0; i < tabPosition->actions().size(); ++i) { - tabPosMenu->addAction(tabPosition->actions().at(i)); + for(int i=0; i < tabPosition->actions().size(); ++i) { + tabPosMenu->addAction(tabPosition->actions().at(i)); + } + + connect(menu_Window, SIGNAL(hovered(QAction *)), + this, SLOT(updateActionGroup(QAction *))); } - - connect(menu_Window, SIGNAL(hovered(QAction *)), - this, SLOT(updateActionGroup(QAction *))); menu_Window->addMenu(tabPosMenu); /* */ /* Scrollbar */ - scrollBarPosition = new QActionGroup(this); - QAction *scrollNone = new QAction(tr("&None"), this); - QAction *scrollRight = new QAction(tr("&Right"), this); - QAction *scrollLeft = new QAction(tr("&Left"), this); + if (scrollBarPosition == NULL) { + scrollBarPosition = new QActionGroup(this); + QAction *scrollNone = new QAction(tr("&None"), this); + QAction *scrollRight = new QAction(tr("&Right"), this); + QAction *scrollLeft = new QAction(tr("&Left"), this); + /* order of insertion is dep. on QTermWidget::ScrollBarPosition enum */ + scrollBarPosition->addAction(scrollNone); + scrollBarPosition->addAction(scrollLeft); + scrollBarPosition->addAction(scrollRight); - /* order of insertion is dep. on QTermWidget::ScrollBarPosition enum */ - scrollBarPosition->addAction(scrollNone); - scrollBarPosition->addAction(scrollLeft); - scrollBarPosition->addAction(scrollRight); + for(int i = 0; i < scrollBarPosition->actions().size(); ++i) + scrollBarPosition->actions().at(i)->setCheckable(true); - for(int i = 0; i < scrollBarPosition->actions().size(); ++i) - scrollBarPosition->actions().at(i)->setCheckable(true); - - if( Properties::Instance()->scrollBarPos < scrollBarPosition->actions().size() ) - scrollBarPosition->actions().at(Properties::Instance()->scrollBarPos)->setChecked(true); - - connect(scrollBarPosition, SIGNAL(triggered(QAction *)), + if( Properties::Instance()->scrollBarPos < scrollBarPosition->actions().size() ) + scrollBarPosition->actions().at(Properties::Instance()->scrollBarPos)->setChecked(true); + connect(scrollBarPosition, SIGNAL(triggered(QAction *)), consoleTabulator, SLOT(changeScrollPosition(QAction *)) ); - scrollPosMenu = new QMenu(tr("S&crollbar Layout"), menu_Window); - scrollPosMenu->setObjectName("scrollPosMenu"); + } + if (scrollPosMenu == NULL) { + scrollPosMenu = new QMenu(tr("S&crollbar Layout"), menu_Window); + scrollPosMenu->setObjectName("scrollPosMenu"); - for(int i=0; i < scrollBarPosition->actions().size(); ++i) { - scrollPosMenu->addAction(scrollBarPosition->actions().at(i)); + for(int i=0; i < scrollBarPosition->actions().size(); ++i) { + scrollPosMenu->addAction(scrollBarPosition->actions().at(i)); + } } menu_Window->addMenu(scrollPosMenu); /* Keyboard cursor shape */ - keyboardCursorShape = new QActionGroup(this); - QAction *block = new QAction(tr("&BlockCursor"), this); - QAction *underline = new QAction(tr("&UnderlineCursor"), this); - QAction *ibeam = new QAction(tr("&IBeamCursor"), this); + if (keyboardCursorShape == NULL) { + keyboardCursorShape = new QActionGroup(this); + QAction *block = new QAction(tr("&BlockCursor"), this); + QAction *underline = new QAction(tr("&UnderlineCursor"), this); + QAction *ibeam = new QAction(tr("&IBeamCursor"), this); - /* order of insertion is dep. on QTermWidget::KeyboardCursorShape enum */ - keyboardCursorShape->addAction(block); - keyboardCursorShape->addAction(underline); - keyboardCursorShape->addAction(ibeam); + /* order of insertion is dep. on QTermWidget::KeyboardCursorShape enum */ + keyboardCursorShape->addAction(block); + keyboardCursorShape->addAction(underline); + keyboardCursorShape->addAction(ibeam); + for(int i = 0; i < keyboardCursorShape->actions().size(); ++i) + keyboardCursorShape->actions().at(i)->setCheckable(true); - for(int i = 0; i < keyboardCursorShape->actions().size(); ++i) - keyboardCursorShape->actions().at(i)->setCheckable(true); + if( Properties::Instance()->keyboardCursorShape < keyboardCursorShape->actions().size() ) + keyboardCursorShape->actions().at(Properties::Instance()->keyboardCursorShape)->setChecked(true); - if( Properties::Instance()->keyboardCursorShape < keyboardCursorShape->actions().size() ) - keyboardCursorShape->actions().at(Properties::Instance()->keyboardCursorShape)->setChecked(true); + connect(keyboardCursorShape, SIGNAL(triggered(QAction *)), + consoleTabulator, SLOT(changeKeyboardCursorShape(QAction *)) ); + } - connect(keyboardCursorShape, SIGNAL(triggered(QAction *)), - consoleTabulator, SLOT(changeKeyboardCursorShape(QAction *)) ); + if (keyboardCursorShapeMenu == NULL) { + keyboardCursorShapeMenu = new QMenu(tr("&Keyboard Cursor Shape"), menu_Window); + keyboardCursorShapeMenu->setObjectName("keyboardCursorShapeMenu"); - keyboardCursorShapeMenu = new QMenu(tr("&Keyboard Cursor Shape"), menu_Window); - keyboardCursorShapeMenu->setObjectName("keyboardCursorShapeMenu"); - - for(int i=0; i < keyboardCursorShape->actions().size(); ++i) { - keyboardCursorShapeMenu->addAction(keyboardCursorShape->actions().at(i)); + for(int i=0; i < keyboardCursorShape->actions().size(); ++i) { + keyboardCursorShapeMenu->addAction(keyboardCursorShape->actions().at(i)); + } } menu_Window->addMenu(keyboardCursorShapeMenu); } -void MainWindow::setup_ContextMenu_Actions(QMenu* contextMenu) const -{ - contextMenu->addAction(Properties::Instance()->actions[COPY_SELECTION]); - contextMenu->addAction(Properties::Instance()->actions[PASTE_CLIPBOARD]); - contextMenu->addAction(Properties::Instance()->actions[PASTE_SELECTION]); - contextMenu->addAction(Properties::Instance()->actions[ZOOM_IN]); - contextMenu->addAction(Properties::Instance()->actions[ZOOM_OUT]); - contextMenu->addAction(Properties::Instance()->actions[ZOOM_RESET]); - contextMenu->addSeparator(); - contextMenu->addAction(Properties::Instance()->actions[CLEAR_TERMINAL]); - contextMenu->addAction(Properties::Instance()->actions[SPLIT_HORIZONTAL]); - contextMenu->addAction(Properties::Instance()->actions[SPLIT_VERTICAL]); - #warning TODO/FIXME: disable the action when there is only one terminal - contextMenu->addAction(Properties::Instance()->actions[SUB_COLLAPSE]); - contextMenu->addSeparator(); - contextMenu->addAction(Properties::Instance()->actions[TOGGLE_MENU]); - contextMenu->addAction(Properties::Instance()->actions[PREFERENCES]); -} - void MainWindow::setupCustomDirs() { const QSettings settings; @@ -451,7 +486,7 @@ void MainWindow::on_consoleTabulator_currentChanged(int) void MainWindow::toggleTabBar() { Properties::Instance()->tabBarless - = !Properties::Instance()->actions[SHOW_TAB_BAR]->isChecked(); + = !actions[SHOW_TAB_BAR]->isChecked(); consoleTabulator->showHideTabBar(); } @@ -461,7 +496,7 @@ void MainWindow::toggleBorderless() show(); setWindowState(Qt::WindowActive); /* don't loose focus on the window */ Properties::Instance()->borderless - = Properties::Instance()->actions[HIDE_WINDOW_BORDERS]->isChecked(); realign(); + = actions[HIDE_WINDOW_BORDERS]->isChecked(); realign(); } void MainWindow::toggleMenu() @@ -478,6 +513,12 @@ void MainWindow::showFullscreen(bool fullscreen) setWindowState(windowState() & ~Qt::WindowFullScreen); } +void MainWindow::toggleBookmarks() +{ + m_bookmarksDock->toggleViewAction()->trigger(); +} + + void MainWindow::closeEvent(QCloseEvent *ev) { if (!Properties::Instance()->askOnExit @@ -550,6 +591,8 @@ void MainWindow::actProperties_triggered() void MainWindow::propertiesChanged() { + rebuildActions(); + QApplication::setStyle(Properties::Instance()->guiStyle); setWindowOpacity(1.0 - Properties::Instance()->appTransparency/100.0); consoleTabulator->setTabPosition((QTabWidget::TabPosition)Properties::Instance()->tabsPos); @@ -560,7 +603,7 @@ void MainWindow::propertiesChanged() m_bookmarksDock->setVisible(Properties::Instance()->useBookmarks && Properties::Instance()->bookmarksVisible); - m_bookmarksDock->toggleViewAction()->setVisible(Properties::Instance()->useBookmarks); + actions[TOGGLE_BOOKMARKS]->setVisible(Properties::Instance()->useBookmarks); if (Properties::Instance()->useBookmarks) { @@ -569,7 +612,6 @@ void MainWindow::propertiesChanged() onCurrentTitleChanged(consoleTabulator->currentIndex()); - Properties::Instance()->saveSettings(); realign(); } @@ -585,8 +627,9 @@ void MainWindow::realign() geometry.moveCenter(desktop.center()); // do not use 0 here - we need to calculate with potential panel on top geometry.setTop(desktop.top()); - - setGeometry(geometry); + if (geometry != this->geometry()) { + setGeometry(geometry); + } } } @@ -645,7 +688,12 @@ bool MainWindow::event(QEvent *event) void MainWindow::newTerminalWindow() { - MainWindow *w = new MainWindow(m_initWorkDir, m_initShell, false); + TerminalConfig cfg; + TermWidgetHolder *ch = consoleTabulator->terminalHolder(); + if (ch) + cfg.provideCurrentDirectory(ch->currentTerminal()->impl()->workingDirectory()); + + MainWindow *w = new MainWindow(cfg, false); w->show(); } @@ -662,6 +710,7 @@ void MainWindow::bookmarksDock_visibilityChanged(bool visible) void MainWindow::addNewTab() { + TerminalConfig cfg; if (Properties::Instance()->terminalsPreset == 3) consoleTabulator->preset4Terminals(); else if (Properties::Instance()->terminalsPreset == 2) @@ -669,7 +718,8 @@ void MainWindow::addNewTab() else if (Properties::Instance()->terminalsPreset == 1) consoleTabulator->preset2Horizontal(); else - consoleTabulator->addNewTab(); + consoleTabulator->addNewTab(cfg); + updateDisabledActions(); } void MainWindow::onCurrentTitleChanged(int index) @@ -695,7 +745,7 @@ bool MainWindow::hasMultipleSubterminals() return consoleTabulator->terminalHolder()->findChildren().count() > 1; } -void MainWindow::aboutToShowActionsMenu() +void MainWindow::updateDisabledActions() { const QList actions = menu_Actions->actions(); for (QAction *action : actions) { @@ -705,3 +755,39 @@ void MainWindow::aboutToShowActionsMenu() } } } + + +QMap< QString, QAction * >& MainWindow::leaseActions() { + return actions; +} +#ifdef HAVE_QDBUS + +QDBusObjectPath MainWindow::getActiveTab() +{ + return qobject_cast(consoleTabulator->currentWidget())->getDbusPath(); +} + +QList MainWindow::getTabs() +{ + QList tabs; + for (int i = 0; icount(); ++i) + { + tabs.push_back(qobject_cast(consoleTabulator->widget(i))->getDbusPath()); + } + return tabs; + +} + +QDBusObjectPath MainWindow::newTab(const QHash &termArgs) +{ + TerminalConfig cfg = TerminalConfig::fromDbus(termArgs); + int idx = consoleTabulator->addNewTab(cfg); + return qobject_cast(consoleTabulator->widget(idx))->getDbusPath(); +} + +void MainWindow::closeWindow() +{ + close(); +} + +#endif diff --git a/src/mainwindow.h b/src/mainwindow.h index 0b052f1..21b5067 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -22,22 +22,34 @@ #include "ui_qterminal.h" #include +#include + #include "qxtglobalshortcut.h" +#include "terminalconfig.h" +#include "dbusaddressable.h" + class QToolButton; -class MainWindow : public QMainWindow , private Ui::mainWindow +class MainWindow : public QMainWindow, private Ui::mainWindow, public DBusAddressable { Q_OBJECT public: - MainWindow(const QString& work_dir, const QString& command, + MainWindow(TerminalConfig& cfg, bool dropMode, QWidget * parent = 0, Qt::WindowFlags f = 0); ~MainWindow(); - bool dropMode() const { return m_dropMode; } - void setup_ContextMenu_Actions(QMenu* contextMenu) const; + bool dropMode() { return m_dropMode; } + QMap & leaseActions(); + + #ifdef HAVE_QDBUS + QDBusObjectPath getActiveTab(); + QList getTabs(); + QDBusObjectPath newTab(const QHash &termArgs); + void closeWindow(); + #endif protected: bool event(QEvent* event); @@ -46,16 +58,26 @@ private: QActionGroup *tabPosition, *scrollBarPosition, *keyboardCursorShape; QMenu *tabPosMenu, *scrollPosMenu, *keyboardCursorShapeMenu; - QString m_initWorkDir; - QString m_initShell; + // A parent object for QObjects that are created dynamically based on settings + // Used to simplify the setting cleanup on reconfiguration: deleting settingOwner frees all related QObjects + QWidget *settingOwner; + + QMenu *presetsMenu; + + TerminalConfig m_config; QDockWidget *m_bookmarksDock; void setup_Action(const char *name, QAction *action, const char *defaultShortcut, const QObject *receiver, const char *slot, QMenu *menu = NULL, const QVariant &data = QVariant()); + QMap< QString, QAction * > actions; + + void rebuildActions(); + void setup_FileMenu_Actions(); void setup_ActionsMenu_Actions(); void setup_ViewMenu_Actions(); + void setup_ContextMenu_Actions(); void setupCustomDirs(); void closeEvent(QCloseEvent*); @@ -70,6 +92,10 @@ private: bool hasMultipleTabs(); bool hasMultipleSubterminals(); +public slots: + void showHide(); + void updateDisabledActions(); + private slots: void on_consoleTabulator_currentChanged(int); void propertiesChanged(); @@ -77,12 +103,12 @@ private slots: void actProperties_triggered(); void updateActionGroup(QAction *); + void toggleBookmarks(); void toggleBorderless(); void toggleTabBar(); void toggleMenu(); void showFullscreen(bool fullscreen); - void showHide(); void setKeepOpen(bool value); void find(); @@ -93,6 +119,5 @@ private slots: void addNewTab(); void onCurrentTitleChanged(int index); - void aboutToShowActionsMenu(); }; #endif //MAINWINDOW_H diff --git a/src/org.lxqt.QTerminal.Process.xml b/src/org.lxqt.QTerminal.Process.xml new file mode 100644 index 0000000..7b231c8 --- /dev/null +++ b/src/org.lxqt.QTerminal.Process.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/org.lxqt.QTerminal.Tab.xml b/src/org.lxqt.QTerminal.Tab.xml new file mode 100644 index 0000000..f11c703 --- /dev/null +++ b/src/org.lxqt.QTerminal.Tab.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/src/org.lxqt.QTerminal.Terminal.xml b/src/org.lxqt.QTerminal.Terminal.xml new file mode 100644 index 0000000..4b76d7f --- /dev/null +++ b/src/org.lxqt.QTerminal.Terminal.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/org.lxqt.QTerminal.Window.xml b/src/org.lxqt.QTerminal.Window.xml new file mode 100644 index 0000000..a791ea6 --- /dev/null +++ b/src/org.lxqt.QTerminal.Window.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/properties.cpp b/src/properties.cpp index b7b2bd1..b904634 100644 --- a/src/properties.cpp +++ b/src/properties.cpp @@ -17,10 +17,12 @@ ***************************************************************************/ #include +#include #include "properties.h" #include "config.h" - +#include "mainwindow.h" +#include "qterminalapp.h" Properties * Properties::m_instance = 0; @@ -45,7 +47,6 @@ Properties::Properties(const QString& filename) Properties::~Properties() { qDebug("Properties destructor called"); - saveSettings(); delete m_settings; m_instance = 0; } @@ -69,17 +70,10 @@ void Properties::loadSettings() highlightCurrentTerminal = m_settings->value("highlightCurrentTerminal", true).toBool(); - font = qvariant_cast(m_settings->value("font", defaultFont())); - - m_settings->beginGroup("Shortcuts"); - QStringList keys = m_settings->childKeys(); - foreach( QString key, keys ) - { - QKeySequence sequence = QKeySequence( m_settings->value( key ).toString() ); - if( Properties::Instance()->actions.contains( key ) ) - Properties::Instance()->actions[ key ]->setShortcut( sequence ); - } - m_settings->endGroup(); + font = QFont(qvariant_cast(m_settings->value("fontFamily", defaultFont().family())), + qvariant_cast(m_settings->value("fontSize", defaultFont().pointSize()))); + //Legacy font setting + font = qvariant_cast(m_settings->value("font", font)); mainWindowSize = m_settings->value("MainWindow/size").toSize(); mainWindowPosition = m_settings->value("MainWindow/pos").toPoint(); @@ -127,7 +121,8 @@ void Properties::loadSettings() // bookmarks useBookmarks = m_settings->value("UseBookmarks", false).toBool(); bookmarksVisible = m_settings->value("BookmarksVisible", true).toBool(); - bookmarksFile = m_settings->value("BookmarksFile", QFileInfo(m_settings->fileName()).canonicalPath()+"/qterminal_bookmarks.xml").toString(); + const QString s = QFileInfo(m_settings->fileName()).canonicalPath() + QString::fromLatin1("/qterminal_bookmarks.xml"); + bookmarksFile = m_settings->value("BookmarksFile", s).toString(); terminalsPreset = m_settings->value("TerminalsPreset", 0).toInt(); @@ -141,6 +136,9 @@ void Properties::loadSettings() changeWindowTitle = m_settings->value("ChangeWindowTitle", true).toBool(); changeWindowIcon = m_settings->value("ChangeWindowIcon", true).toBool(); + + confirmMultilinePaste = m_settings->value("ConfirmMultilinePaste", false).toBool(); + trimPastedTrailingNewlines = m_settings->value("TrimPastedTrailingNewlines", false).toBool(); } void Properties::saveSettings() @@ -148,15 +146,21 @@ void Properties::saveSettings() m_settings->setValue("guiStyle", guiStyle); m_settings->setValue("colorScheme", colorScheme); m_settings->setValue("highlightCurrentTerminal", highlightCurrentTerminal); - m_settings->setValue("font", font); + m_settings->setValue("fontFamily", font.family()); + m_settings->setValue("fontSize", font.pointSize()); + //Clobber legacy setting + m_settings->remove("font"); m_settings->beginGroup("Shortcuts"); - QMapIterator< QString, QAction * > it(actions); + MainWindow *mainWindow = QTerminalApp::Instance()->getWindowList()[0]; + assert(mainWindow != NULL); + + QMapIterator< QString, QAction * > it(mainWindow->leaseActions()); while( it.hasNext() ) { it.next(); QStringList sequenceStrings; - foreach (QKeySequence shortcut, it.value()->shortcuts()) + foreach (const QKeySequence &shortcut, it.value()->shortcuts()) sequenceStrings.append(shortcut.toString()); m_settings->setValue(it.key(), sequenceStrings.join('|')); } @@ -218,6 +222,10 @@ void Properties::saveSettings() m_settings->setValue("ChangeWindowTitle", changeWindowTitle); m_settings->setValue("ChangeWindowIcon", changeWindowIcon); + + + m_settings->setValue("ConfirmMultilinePaste", confirmMultilinePaste); + m_settings->setValue("TrimPastedTrailingNewlines", trimPastedTrailingNewlines); } void Properties::migrate_settings() diff --git a/src/properties.h b/src/properties.h index dbe12c2..c448a19 100644 --- a/src/properties.h +++ b/src/properties.h @@ -22,7 +22,6 @@ #include #include #include -#include typedef QString Session; @@ -95,8 +94,8 @@ class Properties bool changeWindowTitle; bool changeWindowIcon; - QMap< QString, QAction * > actions; - + bool confirmMultilinePaste; + bool trimPastedTrailingNewlines; private: diff --git a/src/propertiesdialog.cpp b/src/propertiesdialog.cpp index 82ad5b1..2c039f9 100644 --- a/src/propertiesdialog.cpp +++ b/src/propertiesdialog.cpp @@ -26,6 +26,7 @@ #include "properties.h" #include "fontdialog.h" #include "config.h" +#include "qterminalapp.h" PropertiesDialog::PropertiesDialog(QWidget *parent) @@ -136,6 +137,10 @@ PropertiesDialog::PropertiesDialog(QWidget *parent) changeWindowTitleCheckBox->setChecked(Properties::Instance()->changeWindowTitle); changeWindowIconCheckBox->setChecked(Properties::Instance()->changeWindowIcon); + + trimPastedTrailingNewlinesCheckBox->setChecked(Properties::Instance()->trimPastedTrailingNewlines); + confirmMultilinePasteCheckBox->setChecked(Properties::Instance()->confirmMultilinePaste); + } @@ -203,6 +208,9 @@ void PropertiesDialog::apply() Properties::Instance()->changeWindowTitle = changeWindowTitleCheckBox->isChecked(); Properties::Instance()->changeWindowIcon = changeWindowIconCheckBox->isChecked(); + Properties::Instance()->trimPastedTrailingNewlines = trimPastedTrailingNewlinesCheckBox->isChecked(); + Properties::Instance()->confirmMultilinePaste = confirmMultilinePasteCheckBox->isChecked(); + emit propertiesChanged(); } @@ -226,7 +234,7 @@ void PropertiesDialog::changeFontButton_clicked() void PropertiesDialog::chooseBackgroundImageButton_clicked() { QString filename = QFileDialog::getOpenFileName( - this, tr("Open or create bookmarks file"), + this, tr("Choose a background image"), QString(), tr("Images (*.bmp *.png *.xpm *.jpg)")); if (!filename.isNull()) backgroundImageLineEdit->setText(filename); @@ -234,7 +242,8 @@ void PropertiesDialog::chooseBackgroundImageButton_clicked() void PropertiesDialog::saveShortcuts() { - QList< QString > shortcutKeys = Properties::Instance()->actions.keys(); + QMap actions = QTerminalApp::Instance()->getWindowList()[0]->leaseActions(); + QList< QString > shortcutKeys = actions.keys(); int shortcutCount = shortcutKeys.count(); shortcutsWidget->setRowCount( shortcutCount ); @@ -242,7 +251,7 @@ void PropertiesDialog::saveShortcuts() for( int x=0; x < shortcutCount; x++ ) { QString keyValue = shortcutKeys.at(x); - QAction *keyAction = Properties::Instance()->actions[keyValue]; + QAction *keyAction = actions[keyValue]; QTableWidgetItem *item = shortcutsWidget->item(x, 1); QKeySequence sequence = QKeySequence(item->text()); @@ -253,11 +262,13 @@ void PropertiesDialog::saveShortcuts() shortcuts.append(QKeySequence(sequenceString)); keyAction->setShortcuts(shortcuts); } + Properties::Instance()->saveSettings(); } void PropertiesDialog::setupShortcuts() { - QList< QString > shortcutKeys = Properties::Instance()->actions.keys(); + QMap actions = QTerminalApp::Instance()->getWindowList()[0]->leaseActions(); + QList< QString > shortcutKeys = actions.keys(); int shortcutCount = shortcutKeys.count(); shortcutsWidget->setRowCount( shortcutCount ); @@ -265,10 +276,10 @@ void PropertiesDialog::setupShortcuts() for( int x=0; x < shortcutCount; x++ ) { QString keyValue = shortcutKeys.at(x); - QAction *keyAction = Properties::Instance()->actions[keyValue]; + QAction *keyAction = actions[keyValue]; QStringList sequenceStrings; - foreach (QKeySequence shortcut, keyAction->shortcuts()) + foreach (const QKeySequence &shortcut, keyAction->shortcuts()) sequenceStrings.append(shortcut.toString()); QTableWidgetItem *itemName = new QTableWidgetItem( tr(keyValue.toStdString().c_str()) ); diff --git a/src/qterminalapp.h b/src/qterminalapp.h new file mode 100644 index 0000000..9cf952b --- /dev/null +++ b/src/qterminalapp.h @@ -0,0 +1,62 @@ +#ifndef QTERMINALAPP_H +#define QTERMINALAPP_H + +#include +#ifdef HAVE_QDBUS + #include +#endif + + +#include "mainwindow.h" + + +class QTerminalApp : public QApplication +{ +Q_OBJECT + +public: + MainWindow *newWindow(bool dropMode, TerminalConfig &cfg); + QList getWindowList(); + void addWindow(MainWindow *window); + void removeWindow(MainWindow *window); + static QTerminalApp *Instance(int &argc, char **argv); + static QTerminalApp *Instance(); + QString &getWorkingDirectory(); + void setWorkingDirectory(const QString &wd); + + #ifdef HAVE_QDBUS + void registerOnDbus(); + QList getWindows(); + QDBusObjectPath newWindow(const QHash &termArgs); + QDBusObjectPath getActiveWindow(); + bool isDropMode(); + bool toggleDropdown(); + #endif + + static void cleanup(); + +private: + QString m_workDir; + QList m_windowList; + static QTerminalApp *m_instance; + QTerminalApp(int &argc, char **argv); + ~QTerminalApp(){}; +}; + +template T* findParent(QObject *child) +{ + QObject *maybeT = child; + while (true) + { + if (maybeT == NULL) + { + return NULL; + } + T *holder = qobject_cast(maybeT); + if (holder) + return holder; + maybeT = maybeT->parent(); + } +} + +#endif \ No newline at end of file diff --git a/src/tabwidget.cpp b/src/tabwidget.cpp index ce860cb..e0a815e 100644 --- a/src/tabwidget.cpp +++ b/src/tabwidget.cpp @@ -21,10 +21,12 @@ #include #include +#include "mainwindow.h" #include "termwidgetholder.h" #include "tabwidget.h" #include "config.h" #include "properties.h" +#include "qterminalapp.h" #define TAB_INDEX_PROPERTY "tab_index" @@ -59,26 +61,17 @@ TermWidgetHolder * TabWidget::terminalHolder() return reinterpret_cast(widget(currentIndex())); } -void TabWidget::setWorkDirectory(const QString& dir) -{ - this->work_dir = dir; -} -int TabWidget::addNewTab(const QString & shell_program) +int TabWidget::addNewTab(TerminalConfig config) { tabNumerator++; QString label = QString(tr("Shell No. %1")).arg(tabNumerator); TermWidgetHolder *ch = terminalHolder(); - QString cwd(work_dir); - if (Properties::Instance()->useCWD && ch) - { - cwd = ch->currentTerminal()->impl()->workingDirectory(); - if (cwd.isEmpty()) - cwd = work_dir; - } + if (ch) + config.provideCurrentDirectory(ch->currentTerminal()->impl()->workingDirectory()); - TermWidgetHolder *console = new TermWidgetHolder(cwd, shell_program, this); + TermWidgetHolder *console = new TermWidgetHolder(config, this); console->setWindowTitle(label); connect(console, SIGNAL(finished()), SLOT(removeFinished())); connect(console, SIGNAL(lastTerminalClosed()), this, SLOT(removeFinished())); @@ -96,29 +89,40 @@ int TabWidget::addNewTab(const QString & shell_program) return index; } -void TabWidget::switchNextSubterminal() +void TabWidget::switchLeftSubterminal() { - terminalHolder()->switchNextSubterminal(); + terminalHolder()->directionalNavigation(NavigationDirection::Left); } -void TabWidget::switchPrevSubterminal() +void TabWidget::switchRightSubterminal() { - terminalHolder()->switchPrevSubterminal(); + terminalHolder()->directionalNavigation(NavigationDirection::Right); +} + +void TabWidget::switchTopSubterminal() { + terminalHolder()->directionalNavigation(NavigationDirection::Top); +} + +void TabWidget::switchBottomSubterminal() { + terminalHolder()->directionalNavigation(NavigationDirection::Bottom); } void TabWidget::splitHorizontally() { terminalHolder()->splitHorizontal(terminalHolder()->currentTerminal()); + findParent(this)->updateDisabledActions(); } void TabWidget::splitVertically() { terminalHolder()->splitVertical(terminalHolder()->currentTerminal()); + findParent(this)->updateDisabledActions(); } void TabWidget::splitCollapse() { terminalHolder()->splitCollapse(terminalHolder()->currentTerminal()); + findParent(this)->updateDisabledActions(); } void TabWidget::copySelection() @@ -206,10 +210,11 @@ void TabWidget::renameTabsAfterRemove() void TabWidget::contextMenuEvent(QContextMenuEvent *event) { QMenu menu(this); + QMap< QString, QAction * > actions = findParent(this)->leaseActions(); QAction *close = menu.addAction(QIcon::fromTheme("document-close"), tr("Close session")); - QAction *rename = menu.addAction(Properties::Instance()->actions[RENAME_SESSION]->text()); - rename->setShortcut(Properties::Instance()->actions[RENAME_SESSION]->shortcut()); + QAction *rename = menu.addAction(actions[RENAME_SESSION]->text()); + rename->setShortcut(actions[RENAME_SESSION]->shortcut()); rename->blockSignals(true); int tabIndex = tabBar()->tabAt(event->pos()); @@ -230,7 +235,10 @@ bool TabWidget::eventFilter(QObject *obj, QEvent *event) // clicks on free space - open new tab int index = tabBar()->tabAt(e->pos()); if (index == -1) - addNewTab(); + { + TerminalConfig defaultConfig; + addNewTab(defaultConfig); + } else renameSession(index); return true; @@ -299,6 +307,7 @@ int TabWidget::switchToRight() setCurrentIndex(next_pos); else setCurrentIndex(0); + findParent(this)->updateDisabledActions(); return currentIndex(); } @@ -309,6 +318,7 @@ int TabWidget::switchToLeft() setCurrentIndex(count() - 1); else setCurrentIndex(previous_pos); + findParent(this)->updateDisabledActions(); return currentIndex(); } @@ -426,33 +436,36 @@ void TabWidget::loadSession() void TabWidget::preset2Horizontal() { - int ix = TabWidget::addNewTab(); + TerminalConfig defaultConfig; + int ix = TabWidget::addNewTab(defaultConfig); TermWidgetHolder* term = reinterpret_cast(widget(ix)); term->splitHorizontal(term->currentTerminal()); // switch to the 1st terminal - term->switchNextSubterminal(); + term->directionalNavigation(NavigationDirection::Left); } void TabWidget::preset2Vertical() { - int ix = TabWidget::addNewTab(); + TerminalConfig defaultConfig; + int ix = TabWidget::addNewTab(defaultConfig); TermWidgetHolder* term = reinterpret_cast(widget(ix)); term->splitVertical(term->currentTerminal()); // switch to the 1st terminal - term->switchNextSubterminal(); + term->directionalNavigation(NavigationDirection::Left); } void TabWidget::preset4Terminals() { - int ix = TabWidget::addNewTab(); + TerminalConfig defaultConfig; + int ix = TabWidget::addNewTab(defaultConfig); TermWidgetHolder* term = reinterpret_cast(widget(ix)); term->splitVertical(term->currentTerminal()); term->splitHorizontal(term->currentTerminal()); - term->switchNextSubterminal(); - term->switchNextSubterminal(); + term->directionalNavigation(NavigationDirection::Left); + term->splitHorizontal(term->currentTerminal()); // switch to the 1st terminal - term->switchNextSubterminal(); + term->directionalNavigation(NavigationDirection::Top); } void TabWidget::showHideTabBar() diff --git a/src/tabwidget.h b/src/tabwidget.h index f532446..46def80 100644 --- a/src/tabwidget.h +++ b/src/tabwidget.h @@ -21,7 +21,14 @@ #include #include +#include +#ifdef HAVE_QDBUS + #include + #include "dbusaddressable.h" +#endif + +#include "terminalconfig.h" #include "properties.h" class TermWidgetHolder; @@ -40,7 +47,7 @@ public: void showHideTabBar(); public slots: - int addNewTab(const QString& shell_program = QString()); + int addNewTab(TerminalConfig cfg); void removeTab(int); void removeCurrentTab(); int switchToRight(); @@ -50,10 +57,12 @@ public slots: void moveRight(); void renameSession(int); void renameCurrentSession(); - void setWorkDirectory(const QString&); - void switchNextSubterminal(); - void switchPrevSubterminal(); + void switchLeftSubterminal(); + void switchRightSubterminal(); + void switchTopSubterminal(); + void switchBottomSubterminal(); + void splitHorizontally(); void splitVertically(); void splitCollapse(); @@ -100,7 +109,6 @@ protected slots: private: int tabNumerator; - QString work_dir; /* re-order naming of the tabs then removeCurrentTab() */ void renameTabsAfterRemove(); }; diff --git a/src/terminalconfig.cpp b/src/terminalconfig.cpp new file mode 100644 index 0000000..ca4f679 --- /dev/null +++ b/src/terminalconfig.cpp @@ -0,0 +1,105 @@ + + +#include +#include + +#include "qterminalapp.h" +#include "terminalconfig.h" +#include "properties.h" +#include "termwidget.h" + +TerminalConfig::TerminalConfig(const QString & wdir, const QString & shell) +{ + m_workingDirectory = wdir; + m_shell = shell; +} + +TerminalConfig::TerminalConfig() +{ +} + +TerminalConfig::TerminalConfig(const TerminalConfig &cfg) + : m_currentDirectory(cfg.m_currentDirectory), + m_workingDirectory(cfg.m_workingDirectory), + m_shell(cfg.m_shell) {} + +QString TerminalConfig::getWorkingDirectory() +{ + if (!m_workingDirectory.isEmpty()) + return m_workingDirectory; + if (Properties::Instance()->useCWD && !m_currentDirectory.isEmpty()) + return m_currentDirectory; + return QTerminalApp::Instance()->getWorkingDirectory(); +} + +QString TerminalConfig::getShell() +{ + if (!m_shell.isEmpty()) + return m_shell; + if (!Properties::Instance()->shell.isEmpty()) + return Properties::Instance()->shell; + QByteArray envShell = qgetenv("SHELL"); + if (envShell.constData() != NULL) + { + QString shellString = QString(envShell); + if (!shellString.isEmpty()) + return shellString; + } + return QString(); +} + +void TerminalConfig::setWorkingDirectory(const QString &val) +{ + m_workingDirectory = val; +} + +void TerminalConfig::setShell(const QString &val) +{ + m_shell = val; +} + +void TerminalConfig::provideCurrentDirectory(const QString &val) +{ + m_currentDirectory = val; +} + + + +#if HAVE_QDBUS + +#define DBUS_ARG_WORKDIR "workingDirectory" +#define DBUS_ARG_SHELL "shell" + +TerminalConfig TerminalConfig::fromDbus(const QHash &termArgsConst, TermWidget *toSplit) +{ + QHash termArgs(termArgsConst); + if (toSplit != NULL && !termArgs.contains(DBUS_ARG_WORKDIR)) + { + termArgs[DBUS_ARG_WORKDIR] = QVariant(toSplit->impl()->workingDirectory()); + } + return TerminalConfig::fromDbus(termArgs); +} + +static QString variantToString(QVariant variant, QString &defaultVal) +{ + if (variant.type() == QVariant::String) + return qvariant_cast(variant); + return defaultVal; +} + +TerminalConfig TerminalConfig::fromDbus(const QHash &termArgs) +{ + QString wdir(""); + QString shell(Properties::Instance()->shell); + if (termArgs.contains(DBUS_ARG_WORKDIR)) + { + wdir = variantToString(termArgs[DBUS_ARG_WORKDIR], wdir); + } + if (termArgs.contains(DBUS_ARG_SHELL)) { + shell = variantToString(termArgs[DBUS_ARG_SHELL], shell); + } + return TerminalConfig(wdir, shell); +} + +#endif + diff --git a/src/terminalconfig.h b/src/terminalconfig.h new file mode 100644 index 0000000..5823fae --- /dev/null +++ b/src/terminalconfig.h @@ -0,0 +1,38 @@ + +#ifndef TERMINALCONFIG_H +#define TERMINALCONFIG_H + +#include +#include +#include + +class TermWidget; + +class TerminalConfig +{ + public: + TerminalConfig(const QString & wdir, const QString & shell); + TerminalConfig(const TerminalConfig &cfg); + TerminalConfig(); + + QString getWorkingDirectory(); + QString getShell(); + + void setWorkingDirectory(const QString &val); + void setShell(const QString &val); + void provideCurrentDirectory(const QString &val); + + #ifdef HAVE_QDBUS + static TerminalConfig fromDbus(const QHash &termArgs); + static TerminalConfig fromDbus(const QHash &termArgs, TermWidget *toSplit); + #endif + + + private: + // True when + QString m_currentDirectory; + QString m_workingDirectory; + QString m_shell; +}; + +#endif \ No newline at end of file diff --git a/src/termwidget.cpp b/src/termwidget.cpp index b02b381..6ffd7cb 100644 --- a/src/termwidget.cpp +++ b/src/termwidget.cpp @@ -20,16 +20,28 @@ #include #include #include +#include +#include +#include +#include +#ifdef HAVE_QDBUS + #include + #include "termwidgetholder.h" + #include "terminaladaptor.h" +#endif + + +#include "mainwindow.h" #include "termwidget.h" #include "config.h" #include "properties.h" -#include "mainwindow.h" +#include "qterminalapp.h" static int TermWidgetCount = 0; -TermWidgetImpl::TermWidgetImpl(const QString & wdir, const QString & shell, QWidget * parent) +TermWidgetImpl::TermWidgetImpl(TerminalConfig &cfg, QWidget * parent) : QTermWidget(0, parent) { TermWidgetCount++; @@ -43,17 +55,12 @@ TermWidgetImpl::TermWidgetImpl(const QString & wdir, const QString & shell, QWid setHistorySize(5000); - if (!wdir.isNull()) - setWorkingDirectory(wdir); + setWorkingDirectory(cfg.getWorkingDirectory()); - if (shell.isNull()) + QString shell = cfg.getShell(); + if (!shell.isEmpty()) { - if (!Properties::Instance()->shell.isNull()) - setShellProgram(Properties::Instance()->shell); - } - else - { - qDebug() << "Settings custom shell program:" << shell; + qDebug() << "Shell program:" << shell; QStringList parts = shell.split(QRegExp("\\s+"), QString::SkipEmptyParts); qDebug() << parts; setShellProgram(parts.at(0)); @@ -109,14 +116,14 @@ void TermWidgetImpl::propertiesChanged() switch(Properties::Instance()->keyboardCursorShape) { case 1: - setKeyboardCursorShape(QTermWidget::UnderlineCursor); + setKeyboardCursorShape(QTermWidget::KeyboardCursorShape::UnderlineCursor); break; case 2: - setKeyboardCursorShape(QTermWidget::IBeamCursor); + setKeyboardCursorShape(QTermWidget::KeyboardCursorShape::IBeamCursor); break; default: case 0: - setKeyboardCursorShape(QTermWidget::BlockCursor); + setKeyboardCursorShape(QTermWidget::KeyboardCursorShape::BlockCursor); break; } @@ -125,20 +132,36 @@ void TermWidgetImpl::propertiesChanged() void TermWidgetImpl::customContextMenuCall(const QPoint & pos) { - QMenu* contextMenu = new QMenu(this); + QMenu menu; + QMap actions = findParent(this)->leaseActions(); - QList actions = filterActions(pos); - for (auto& action : actions) + QList extraActions = filterActions(pos); + for (auto& action : extraActions) { - contextMenu->addAction(action); + menu.addAction(action); } - contextMenu->addSeparator(); + if (!actions.isEmpty()) + { + menu.addSeparator(); + } - const MainWindow *main = qobject_cast(window()); - main->setup_ContextMenu_Actions(contextMenu); - - contextMenu->exec(mapToGlobal(pos)); + menu.addAction(actions[COPY_SELECTION]); + menu.addAction(actions[PASTE_CLIPBOARD]); + menu.addAction(actions[PASTE_SELECTION]); + menu.addAction(actions[ZOOM_IN]); + menu.addAction(actions[ZOOM_OUT]); + menu.addAction(actions[ZOOM_RESET]); + menu.addSeparator(); + menu.addAction(actions[CLEAR_TERMINAL]); + menu.addAction(actions[SPLIT_HORIZONTAL]); + menu.addAction(actions[SPLIT_VERTICAL]); + // warning TODO/FIXME: disable the action when there is only one terminal + menu.addAction(actions[SUB_COLLAPSE]); + menu.addSeparator(); + menu.addAction(actions[TOGGLE_MENU]); + menu.addAction(actions[PREFERENCES]); + menu.exec(mapToGlobal(pos)); } void TermWidgetImpl::zoomIn() @@ -171,17 +194,106 @@ void TermWidgetImpl::activateUrl(const QUrl & url, bool fromContextMenu) { } } -TermWidget::TermWidget(const QString & wdir, const QString & shell, QWidget * parent) - : QWidget(parent) +void TermWidgetImpl::pasteSelection() { + paste(QClipboard::Selection); +} + +void TermWidgetImpl::pasteClipboard() +{ + paste(QClipboard::Clipboard); +} + +void TermWidgetImpl::paste(QClipboard::Mode mode) +{ + // Paste Clipboard by simulating keypress events + QString text = QApplication::clipboard()->text(mode); + if ( ! text.isEmpty() ) + { + text.replace("\r\n", "\n"); + text.replace('\n', '\r'); + QString trimmedTrailingNl(text); + trimmedTrailingNl.replace(QRegExp("\\r+$"), ""); + bool isMultiline = trimmedTrailingNl.contains('\r'); + if (!isMultiline && Properties::Instance()->trimPastedTrailingNewlines) + { + text = trimmedTrailingNl; + } + if (Properties::Instance()->confirmMultilinePaste) + { + if (text.contains('\r') && Properties::Instance()->confirmMultilinePaste) + { + QMessageBox confirmation(this); + confirmation.setWindowTitle(tr("Paste multiline text")); + confirmation.setText(tr("Are you sure you want to paste this text?")); + confirmation.setDetailedText(text); + confirmation.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + // Click "Show details..." to show those by default + foreach( QAbstractButton * btn, confirmation.buttons() ) + { + if (confirmation.buttonRole(btn) == QMessageBox::ActionRole && btn->text() == QMessageBox::tr("Show Details...")) + { + btn->clicked(); + break; + } + } + confirmation.setDefaultButton(QMessageBox::Yes); + confirmation.exec(); + if (confirmation.standardButton(confirmation.clickedButton()) != QMessageBox::Yes) + { + return; + } + } + } + + /* TODO: Support bracketedPasteMode + if (bracketedPasteMode()) + { + text.prepend("\e[200~"); + text.append("\e[201~"); + }*/ + sendText(text); + } +} + +bool TermWidget::eventFilter(QObject * obj, QEvent * ev) +{ + if (ev->type() == QEvent::MouseButtonPress) + { + QMouseEvent *mev = (QMouseEvent *)ev; + if ( mev->button() == Qt::MidButton ) + { + impl()->pasteSelection(); + return true; + } + } + return false; +} + +TermWidget::TermWidget(TerminalConfig &cfg, QWidget * parent) + : QWidget(parent), + DBusAddressable("/terminals") +{ + + #ifdef HAVE_QDBUS + registerAdapter(this); + #endif m_border = palette().color(QPalette::Window); - m_term = new TermWidgetImpl(wdir, shell, this); + m_term = new TermWidgetImpl(cfg, this); setFocusProxy(m_term); m_layout = new QVBoxLayout; setLayout(m_layout); m_layout->addWidget(m_term); + foreach (QObject *o, m_term->children()) + { + // Find TerminalDisplay + if (!o->isWidgetType() || qobject_cast(o)->isHidden()) + continue; + o->installEventFilter(this); + } + propertiesChanged(); @@ -216,10 +328,52 @@ void TermWidget::term_termLostFocus() void TermWidget::paintEvent (QPaintEvent *) { - QPainter p(this); - QPen pen(m_border); - pen.setWidth(30); - pen.setBrush(m_border); - p.setPen(pen); - p.drawRect(0, 0, width()-1, height()-1); + if (Properties::Instance()->highlightCurrentTerminal) + { + QPainter p(this); + QPen pen(m_border); + pen.setWidth(3); + pen.setBrush(m_border); + p.setPen(pen); + p.drawRect(0, 0, width()-1, height()-1); + } } + +#if HAVE_QDBUS + +QDBusObjectPath TermWidget::splitHorizontal(const QHash &termArgs) +{ + TermWidgetHolder *holder = findParent(this); + assert(holder != NULL); + TerminalConfig cfg = TerminalConfig::fromDbus(termArgs, this); + return holder->split(this, Qt::Horizontal, cfg)->getDbusPath(); +} + +QDBusObjectPath TermWidget::splitVertical(const QHash &termArgs) +{ + TermWidgetHolder *holder = findParent(this); + assert(holder != NULL); + TerminalConfig cfg = TerminalConfig::fromDbus(termArgs, this); + return holder->split(this, Qt::Vertical, cfg)->getDbusPath(); +} + +QDBusObjectPath TermWidget::getTab() +{ + return findParent(this)->getDbusPath(); +} + +void TermWidget::closeTerminal() +{ + TermWidgetHolder *holder = findParent(this); + holder->splitCollapse(this); +} + +void TermWidget::sendText(const QString text) +{ + if (impl()) + { + impl()->sendText(text); + } +} + +#endif diff --git a/src/termwidget.h b/src/termwidget.h index 24813e4..e7cb550 100644 --- a/src/termwidget.h +++ b/src/termwidget.h @@ -20,9 +20,11 @@ #define TERMWIDGET_H #include +#include "terminalconfig.h" +#include #include - +#include "dbusaddressable.h" class TermWidgetImpl : public QTermWidget { @@ -32,8 +34,9 @@ class TermWidgetImpl : public QTermWidget public: - TermWidgetImpl(const QString & wdir, const QString & shell=QString(), QWidget * parent=0); + TermWidgetImpl(TerminalConfig &cfg, QWidget * parent=0); void propertiesChanged(); + void paste(QClipboard::Mode mode); signals: void renameSession(); @@ -43,6 +46,8 @@ class TermWidgetImpl : public QTermWidget void zoomIn(); void zoomOut(); void zoomReset(); + void pasteSelection(); + void pasteClipboard(); private slots: void customContextMenuCall(const QPoint & pos); @@ -50,7 +55,7 @@ class TermWidgetImpl : public QTermWidget }; -class TermWidget : public QWidget +class TermWidget : public QWidget, public DBusAddressable { Q_OBJECT @@ -59,13 +64,21 @@ class TermWidget : public QWidget QColor m_border; public: - TermWidget(const QString & wdir, const QString & shell=QString(), QWidget * parent=0); + TermWidget(TerminalConfig &cfg, QWidget * parent=0); void propertiesChanged(); QStringList availableKeyBindings() { return m_term->availableKeyBindings(); } TermWidgetImpl * impl() { return m_term; } + #ifdef HAVE_QDBUS + QDBusObjectPath splitHorizontal(const QHash &termArgs); + QDBusObjectPath splitVertical(const QHash &termArgs); + QDBusObjectPath getTab(); + void sendText(const QString text); + void closeTerminal(); + #endif + signals: void finished(); void renameSession(); @@ -80,6 +93,7 @@ class TermWidget : public QWidget protected: void paintEvent (QPaintEvent * event); + bool eventFilter(QObject * obj, QEvent * evt) override; private slots: void term_termGetFocus(); diff --git a/src/termwidgetholder.cpp b/src/termwidgetholder.cpp index 05ab5fa..b103859 100644 --- a/src/termwidgetholder.cpp +++ b/src/termwidgetholder.cpp @@ -20,18 +20,31 @@ #include #include +#ifdef HAVE_QDBUS + #include + #include "tabadaptor.h" +#endif + +#include "qterminalapp.h" +#include "mainwindow.h" #include "termwidgetholder.h" #include "termwidget.h" #include "properties.h" #include +#include +#include -TermWidgetHolder::TermWidgetHolder(const QString & wdir, const QString & shell, QWidget * parent) - : QWidget(parent), - m_wdir(wdir), - m_shell(shell), - m_currentTerm(0) +TermWidgetHolder::TermWidgetHolder(TerminalConfig &config, QWidget * parent) + : QWidget(parent) + #ifdef HAVE_QDBUS + , DBusAddressable("/tabs") + #endif { + #ifdef HAVE_QDBUS + new TabAdaptor(this); + QDBusConnection::sessionBus().registerObject(getDbusPathString(), this); + #endif setFocusPolicy(Qt::NoFocus); QGridLayout * lay = new QGridLayout(this); lay->setSpacing(0); @@ -39,9 +52,10 @@ TermWidgetHolder::TermWidgetHolder(const QString & wdir, const QString & shell, QSplitter *s = new QSplitter(this); s->setFocusPolicy(Qt::NoFocus); - TermWidget *w = newTerm(); + TermWidget *w = newTerm(config); s->addWidget(w); lay->addWidget(s); + m_currentTerm = w; setLayout(lay); } @@ -124,33 +138,64 @@ void TermWidgetHolder::setWDir(const QString & wdir) m_wdir = wdir; } -void TermWidgetHolder::switchNextSubterminal() -{ - // TODO/FIXME: merge switchPrevSubterminal with switchNextSubterminal - QList l = findChildren(); - int ix = -1; - foreach (TermWidget * w, l) - { - ++ix; - if (w->impl()->hasFocus()) - { - break; - } - } +typedef struct { + QPoint topLeft; + QPoint middle; + QPoint bottomRight; +} NavigationData; - if (ix < l.count()-1) - { - l.at(ix+1)->impl()->setFocus(Qt::OtherFocusReason); - } - else if (ix == l.count()-1) - { - l.at(0)->impl()->setFocus(Qt::OtherFocusReason); +static void transpose(QPoint *point) { + int x = point->x(); + point->setX(point->y()); + point->setY(x); +} + +static void transposeTransform(NavigationData *point) { + transpose(&point->topLeft); + transpose(&point->middle); + transpose(&point->bottomRight); +} + +static void flipTransform(NavigationData *point) { + QPoint oldTopLeft = point->topLeft; + point->topLeft = -(point->bottomRight); + point->bottomRight = -(oldTopLeft); + point->middle = -(point->middle); +} + +static void normalizeToRight(NavigationData *point, NavigationDirection dir) { + switch (dir) { + case Left: + flipTransform(point); + break; + case Right: + // No-op + break; + case Top: + flipTransform(point); + transposeTransform(point); + break; + case Bottom: + transposeTransform(point); + break; + default: + assert("Invalid navigation"); + return; } } -void TermWidgetHolder::switchPrevSubterminal() -{ - // TODO/FIXME: merge switchPrevSubterminal with switchNextSubterminal +static NavigationData getNormalizedDimensions(QWidget *w, NavigationDirection dir) { + NavigationData nd; + nd.topLeft = w->mapTo(w->window(), QPoint(0, 0)); + nd.middle = w->mapTo(w->window(), QPoint(w->width() / 2, w->height() / 2)); + nd.bottomRight = w->mapTo(w->window(), QPoint(w->width(), w->height())); + normalizeToRight(&nd, dir); + return nd; +} + + +void TermWidgetHolder::directionalNavigation(NavigationDirection dir) { + // Find an active widget QList l = findChildren(); int ix = -1; foreach (TermWidget * w, l) @@ -161,14 +206,50 @@ void TermWidgetHolder::switchPrevSubterminal() break; } } - - if (ix > 0) + if (ix > l.count()) { - l.at(ix-1)->impl()->setFocus(Qt::OtherFocusReason); + l.at(0)->impl()->setFocus(Qt::OtherFocusReason); + return; } - else if (ix == 0) + + // Found an active widget + TermWidget *w = l.at(ix); + NavigationData from = getNormalizedDimensions(w, dir); + + // Search parent that contains point of interest (right edge middlepoint) + QPoint poi = QPoint(from.bottomRight.x(), from.middle.y()); + + // Perform a search for a TermWidget, where x() is strictly higher than + // poi.x(), y() is strictly less than poi.y(), and prioritizing, in order, + // lower x(), and lower distance between poi.y() and corners. + + // Only "Right navigation" implementation is necessary -- other cases + // are normalized to this one. + + l = findChildren(); + int lowestX = INT_MAX; + int lowestMidpointDistance = INT_MAX; + TermWidget *fittest = NULL; + foreach (TermWidget * w, l) { - l.at(l.count()-1)->impl()->setFocus(Qt::OtherFocusReason); + NavigationData contenderDims = getNormalizedDimensions(w, dir); + int midpointDistance = std::min( + abs(poi.y() - contenderDims.topLeft.y()), + abs(poi.y() - contenderDims.bottomRight.y()) + ); + if (contenderDims.topLeft.x() > poi.x()) + { + if (contenderDims.topLeft.x() > lowestX) + continue; + if (midpointDistance > lowestMidpointDistance) + continue; + lowestX = contenderDims.topLeft.x(); + lowestMidpointDistance = midpointDistance; + fittest = w; + } + } + if (fittest != NULL) { + fittest->impl()->setFocus(Qt::OtherFocusReason); } } @@ -185,12 +266,14 @@ void TermWidgetHolder::propertiesChanged() void TermWidgetHolder::splitHorizontal(TermWidget * term) { - split(term, Qt::Vertical); + TerminalConfig defaultConfig; + split(term, Qt::Vertical, defaultConfig); } void TermWidgetHolder::splitVertical(TermWidget * term) { - split(term, Qt::Horizontal); + TerminalConfig defaultConfig; + split(term, Qt::Horizontal, defaultConfig); } void TermWidgetHolder::splitCollapse(TermWidget * term) @@ -242,7 +325,7 @@ void TermWidgetHolder::splitCollapse(TermWidget * term) emit finished(); } -void TermWidgetHolder::split(TermWidget *term, Qt::Orientation orientation) +TermWidget * TermWidgetHolder::split(TermWidget *term, Qt::Orientation orientation, TerminalConfig cfg) { QSplitter *parent = qobject_cast(term->parent()); assert(parent); @@ -257,16 +340,9 @@ void TermWidgetHolder::split(TermWidget *term, Qt::Orientation orientation) s->setFocusPolicy(Qt::NoFocus); s->insertWidget(0, term); - // wdir settings - QString wd(m_wdir); - if (Properties::Instance()->useCWD) - { - wd = term->impl()->workingDirectory(); - if (wd.isEmpty()) - wd = m_wdir; - } - - TermWidget * w = newTerm(wd); + cfg.provideCurrentDirectory(term->impl()->workingDirectory()); + + TermWidget * w = newTerm(cfg); s->insertWidget(1, w); s->setSizes(sizes); @@ -274,19 +350,12 @@ void TermWidgetHolder::split(TermWidget *term, Qt::Orientation orientation) parent->setSizes(parentSizes); w->setFocus(Qt::OtherFocusReason); + return w; } -TermWidget *TermWidgetHolder::newTerm(const QString & wdir, const QString & shell) +TermWidget *TermWidgetHolder::newTerm(TerminalConfig &cfg) { - QString wd(wdir); - if (wd.isEmpty()) - wd = m_wdir; - - QString sh(shell); - if (shell.isEmpty()) - sh = m_shell; - - TermWidget *w = new TermWidget(wd, sh, this); + TermWidget *w = new TermWidget(cfg, this); // proxy signals connect(w, SIGNAL(renameSession()), this, SIGNAL(renameSession())); connect(w, SIGNAL(removeCurrentSession()), this, SIGNAL(lastTerminalClosed())); @@ -339,3 +408,40 @@ void TermWidgetHolder::onTermTitleChanged(QString title, QString icon) const if (m_currentTerm == term) emit termTitleChanged(title, icon); } + +#ifdef HAVE_QDBUS + +QDBusObjectPath TermWidgetHolder::getActiveTerminal() +{ + if (m_currentTerm != NULL) + { + return m_currentTerm->getDbusPath(); + } + return QDBusObjectPath(); +} + +QList TermWidgetHolder::getTerminals() +{ + QList terminals; + foreach (TermWidget* w, findChildren()) + { + terminals.push_back(w->getDbusPath()); + } + return terminals; +} + +QDBusObjectPath TermWidgetHolder::getWindow() +{ + return findParent(this)->getDbusPath(); +} + +void TermWidgetHolder::closeTab() +{ + QTabWidget *parent = findParent(this); + int idx = parent->indexOf(this); + assert(idx != -1); + parent->tabCloseRequested(idx); +} + +#endif + diff --git a/src/termwidgetholder.h b/src/termwidgetholder.h index b5aa84b..23bc881 100644 --- a/src/termwidgetholder.h +++ b/src/termwidgetholder.h @@ -21,10 +21,20 @@ #include #include "termwidget.h" +#include "terminalconfig.h" +#include "dbusaddressable.h" class QSplitter; +typedef enum NavigationDirection { + Left, + Right, + Top, + Bottom +} NavigationDirection; + + /*! \brief TermWidget group/session manager. This widget (one per TabWidget tab) is a "proxy" widget beetween TabWidget and @@ -34,11 +44,14 @@ for TabWidget - with its signals and slots. Splitting and collapsing of TermWidgets is done here. */ class TermWidgetHolder : public QWidget +#ifdef HAVE_QDBUS + , public DBusAddressable +#endif { Q_OBJECT public: - TermWidgetHolder(const QString & wdir, const QString & shell=QString(), QWidget * parent=0); + TermWidgetHolder(TerminalConfig &cfg, QWidget * parent=0); ~TermWidgetHolder(); void propertiesChanged(); @@ -50,14 +63,22 @@ class TermWidgetHolder : public QWidget void zoomOut(uint step); TermWidget* currentTerminal(); + TermWidget* split(TermWidget * term, Qt::Orientation orientation, TerminalConfig cfg); + + #ifdef HAVE_QDBUS + QDBusObjectPath getActiveTerminal(); + QList getTerminals(); + QDBusObjectPath getWindow(); + void closeTab(); + #endif + public slots: void splitHorizontal(TermWidget * term); void splitVertical(TermWidget * term); void splitCollapse(TermWidget * term); void setWDir(const QString & wdir); - void switchNextSubterminal(); - void switchPrevSubterminal(); + void directionalNavigation(NavigationDirection dir); void clearActiveTerminal(); void onTermTitleChanged(QString title, QString icon) const; @@ -73,7 +94,7 @@ class TermWidgetHolder : public QWidget TermWidget * m_currentTerm; void split(TermWidget * term, Qt::Orientation orientation); - TermWidget * newTerm(const QString & wdir=QString(), const QString & shell=QString()); + TermWidget * newTerm(TerminalConfig &cfg); private slots: void setCurrentTerminal(TermWidget* term);