parent
684f801180
commit
4c75f9e6c7
@ -0,0 +1,49 @@
|
||||
# Generated CMake files
|
||||
CMakeFiles/
|
||||
CMakeCache.txt
|
||||
*.cmake
|
||||
|
||||
# Generated Visual Studio files
|
||||
/plugins/Debug/
|
||||
/plugins/Release/
|
||||
/plugins/Win*/
|
||||
/Debug/
|
||||
/Release/
|
||||
/Win*/
|
||||
*.sln
|
||||
*.suo
|
||||
*.*sdf
|
||||
*.vc?proj*
|
||||
|
||||
# Generated Makefiles
|
||||
/plugins/Makefile
|
||||
/plugins/*/Makefile
|
||||
/src/Makefile
|
||||
/Makefile
|
||||
|
||||
# Generated Qt files
|
||||
/plugins/**/*.dir/
|
||||
/src/copyqcon.dir/
|
||||
/src/copyq.dir/
|
||||
/src/copyq_*.qm
|
||||
/src/copyq_*.qm.rule
|
||||
/src/qrc_copyq.cxx
|
||||
/src/translations.qrc
|
||||
/src/qrc_translations.cxx
|
||||
|
||||
moc_*
|
||||
ui_*
|
||||
*.depends
|
||||
*.o
|
||||
*.dylib
|
||||
*.moc
|
||||
**/qrc_*.cpp
|
||||
/.qmake.cache
|
||||
/copyq.app
|
||||
copyq.pro.user
|
||||
.DS_Store
|
||||
.qmake.stash
|
||||
*.pyc
|
||||
*.dmg
|
||||
/build
|
||||
*.qm
|
@ -0,0 +1,59 @@
|
||||
# Use latest Ubuntu LTS docker image.
|
||||
image: ubuntu:xenial
|
||||
|
||||
variables:
|
||||
BUILD_DIR: "build"
|
||||
INSTALL_PREFIX: "copyq"
|
||||
SCREENSHOT_DIR: "screenshots"
|
||||
TESTS_LOG_DIR: "logs"
|
||||
|
||||
build:
|
||||
stage: build
|
||||
|
||||
before_script:
|
||||
- utils/gitlab/build-before_script.sh
|
||||
|
||||
script:
|
||||
- utils/gitlab/build-script.sh
|
||||
|
||||
# Upload installed application.
|
||||
artifacts:
|
||||
paths:
|
||||
- "$INSTALL_PREFIX"
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- build
|
||||
|
||||
# Run simple tests (doesn't require GUI)
|
||||
test:
|
||||
stage: test
|
||||
|
||||
before_script:
|
||||
- utils/gitlab/test-before_script.sh
|
||||
|
||||
script:
|
||||
- utils/gitlab/test-script.sh
|
||||
|
||||
dependencies:
|
||||
- build
|
||||
|
||||
# GUI tests (requires X11)
|
||||
test_gui:
|
||||
stage: test
|
||||
|
||||
before_script:
|
||||
- utils/gitlab/test_gui-before_script.sh
|
||||
|
||||
script:
|
||||
- utils/gitlab/test_gui-script.sh
|
||||
|
||||
# Upload screenshots on failure.
|
||||
artifacts:
|
||||
when: on_failure
|
||||
paths:
|
||||
- "$SCREENSHOT_DIR"
|
||||
- "$TESTS_LOG_DIR"
|
||||
|
||||
dependencies:
|
||||
- build
|
@ -0,0 +1,62 @@
|
||||
language: cpp
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
compiler: clang
|
||||
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
env:
|
||||
- COMPILER=g++-4.8
|
||||
- GCOV=gcov-4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
|
||||
- os: linux
|
||||
compiler: clang
|
||||
env:
|
||||
- COMPILER=clang++-3.6
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-precise-3.6
|
||||
packages:
|
||||
- clang-3.6
|
||||
|
||||
cache:
|
||||
apt: true
|
||||
ccache: true
|
||||
directories:
|
||||
- $HOME/.wheelhouse
|
||||
|
||||
before_install:
|
||||
- utils/travis/before-install-${TRAVIS_OS_NAME}.sh
|
||||
|
||||
install:
|
||||
- utils/travis/install-${TRAVIS_OS_NAME}.sh
|
||||
|
||||
script:
|
||||
- utils/travis/script-${TRAVIS_OS_NAME}.sh
|
||||
|
||||
after_success:
|
||||
- utils/travis/after_success-${TRAVIS_OS_NAME}.sh
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: Vax27ifQsc8SlTsLYVbxVJANDAxDroegN6nOPXCN1MLaoh4W2DQ/iGGx+waIOSYig8Sh+AUz2JhCFuMLMVqwFoWY2rxNPBrxhTBjm3aDhylbB+mRECnbInNb0kS3qv4lNDN6lHD4B6K01FWUUiHX14s2JQx4ut+KuwMxxhxyO4Y=
|
||||
file: 'build/*.dmg'
|
||||
file_glob: true
|
||||
skip_cleanup: true
|
||||
overwrite: true
|
||||
on:
|
||||
condition: "$TRAVIS_OS_NAME = osx"
|
||||
tags: true
|
||||
all_branches: true
|
||||
repo: hluk/CopyQ
|
@ -0,0 +1,21 @@
|
||||
Adam Batkin <adam@batkin.net>
|
||||
Giacomo Margarito <giacomomargarito@gmail.com>
|
||||
Greg Carp <grcarpbe@gmail.com>
|
||||
Ilya Plenne <libbkmz.dev@gmail.com>
|
||||
Jörg Thalheim <joerg@higgsboson.tk>
|
||||
Kim Jzhone <jzhone@gmail.com>
|
||||
Kos Ivantsov <kos.ivantsov@gmail.com>
|
||||
lightonflux <lightonflux@znn.info>
|
||||
Lukas Holecek <hluk@email.cz>
|
||||
Marjolein Hoekstra <http://twitter.com/cleverclogs>
|
||||
Martin Lepadusch <mlepadusch@googlemail.com>
|
||||
Matt d'Entremont <mattdentremont@gmail.com>
|
||||
Michal Čihař <michal@cihar.com>
|
||||
Patricio M. Ros <patricioros.dev@gmail.com>
|
||||
Robert Orzanna <robert@orzanna.de>
|
||||
Ryan Wooden <rygwdn@gmail.com>
|
||||
Scott Kostyshak <skostysh@princeton.edu>
|
||||
Sebastian Schuberth <sschuberth@gmail.com>
|
||||
Tomas Nilzon <tomas.nilzon@telia.com>
|
||||
Wilfried Caruel <wilfried.caruel@gmail.com>
|
||||
x2357 <x2357handle@gmail.com>
|
@ -0,0 +1,461 @@
|
||||
v3.0.2
|
||||
- Added script functions for listing synchronized tabs and their paths
|
||||
- Fixed showing window on current screen
|
||||
- Fixed notification position with multiple screens
|
||||
- Fixed rendering items when scrolling
|
||||
- Fixed pasting from main window after switching tabs
|
||||
- Fixed copy/paste to some apps on OS X
|
||||
- Fixed focusing editor when closing completion popup on OS X
|
||||
- Fixed setting temporary file template from script
|
||||
|
||||
v3.0.1
|
||||
- Install themes on OS X
|
||||
- Improve pasting to current window
|
||||
- Fix crash when the first tab is not loaded
|
||||
- Fix crash when reloading tab after closing editor
|
||||
- Fix item rendering and UI elements for high DPI displays
|
||||
- Fix window focus after closing menu or main window on OS X
|
||||
- Fix opening main window on current space on OS X
|
||||
- Fix pasting to some windows on OS X
|
||||
- Fix navigating item list
|
||||
- Fix getting boolean from checkbox in dialog()
|
||||
- Fix default move action for drag'n'drop
|
||||
- Fix exitting on logout when tray is disabled
|
||||
|
||||
v3.0.0
|
||||
- Pinned and protected items
|
||||
- Export/import tabs, configuration and commands in one file
|
||||
- Create and modify commands from script
|
||||
- Create temporary files from script
|
||||
- Create notifications with buttons from script
|
||||
- Take screenshots using script
|
||||
- Allow to process lines on stdout from execute() scriptable using a function
|
||||
- Safer and faster encrypt/decrypt commands (need to be re-added)
|
||||
- Improved menu scriptable function
|
||||
- Improved icon sharpness
|
||||
- Improved plugin architecture
|
||||
- Improved logging and displaying log
|
||||
- Performance and memory consumption improvements
|
||||
- Implemented copy() on OS X
|
||||
- Fixed focusing menu and windows on OS X
|
||||
- Fixed configuration folder path for portable version on Windows
|
||||
- Fixed opening menu for a tab
|
||||
- Fixed using correct GPG version for encryption
|
||||
- Fixed tray menu position in KDE/Plasma
|
||||
|
||||
v2.9.0
|
||||
- Set text style in editor
|
||||
- Search in editor
|
||||
- Quick help in completion popup menu for commands
|
||||
- Easier text selection in item preview
|
||||
- Show whole text and unscaled image in item preview
|
||||
- Improved pasting to windows on Linux/X11
|
||||
- Fixed global shortcuts at application start on Linux/X11
|
||||
- Fixed closing application from installer on Windows
|
||||
- Fixed showing item preview at start
|
||||
- Fixed saving position of new tabs and tab lists
|
||||
|
||||
v2.8.3
|
||||
- Search items from tray menu
|
||||
- Added support for animated gifs (played when selected)
|
||||
- Added special formats for automatic commands to sync and store clipboard
|
||||
- Added auto-completion for command editor
|
||||
- Added scriptable variables for MIME types
|
||||
- Fix encryption with new OpenPGP
|
||||
- Fix passing big data to commands on Windows
|
||||
|
||||
v2.8.2
|
||||
- Simplify appearance of items with notes and tags
|
||||
- Support for drag'n'dropping images to more applications
|
||||
- Added list widget for custom dialog
|
||||
- Fixed opening windows on current screen
|
||||
- Fixed tray icon appearance on Linux
|
||||
- Fixed focusing tray menu from command
|
||||
- Fixed dialog button translation on Windows
|
||||
- Fixed passing big data to commands
|
||||
|
||||
v2.8.1
|
||||
- All Qt messages are logged
|
||||
- Fixed and improved commands for Tags plugin
|
||||
- Fixed removing last items when changing item limit
|
||||
- Fixed library paths for OS X
|
||||
- Fixed pasting items on Windows
|
||||
- Fixed copying from script on Windows
|
||||
|
||||
v2.8.0
|
||||
- Insert images in editor
|
||||
- Show simple items options
|
||||
- Item preview window
|
||||
- Move to Qt 5 on Windows and newer Linux distros
|
||||
- Faster item content listing
|
||||
- Simple filter for Log dialog
|
||||
- Smooth icons on OS X
|
||||
- Fixed system icons
|
||||
- Fixed pasting animated images
|
||||
- Fixed occasional crashes when finalizing commands with Qt 5
|
||||
- Fixed opening log speed on Windows
|
||||
- Lithuanian translation
|
||||
|
||||
v2.7.1
|
||||
- Colorize items with command
|
||||
- Drag'n'drop items in selection order
|
||||
- Fixed item selection with "next" and "previous" commands
|
||||
- Fixed encrypting/decrypting items on Windows
|
||||
- Fixed occasional client crashes at exit
|
||||
- Fixed editor command on OS X
|
||||
|
||||
v2.7.0
|
||||
- Log accessible from GUI
|
||||
- Performance and memory usage improvements
|
||||
- Added scriptable function to set current tab (setCurrentTab())
|
||||
- Added scriptable function to modify new items (setData())
|
||||
- Appearance fixes
|
||||
- Simplified window management
|
||||
- Improved pasting to current window on Windows
|
||||
- Window geometry fixes
|
||||
- Command with Enter shortcut overrides item activate action
|
||||
|
||||
v2.6.1
|
||||
- Moved configuration from registry on Windows
|
||||
- Fixed shortcuts on Windows
|
||||
- Fixed window geometry restoring
|
||||
|
||||
v2.6.0
|
||||
- Show item notes in tray and window title
|
||||
- Removed broken console executable on Windows
|
||||
- Dutch translation
|
||||
- Added env() and setEnv() to access and modify environment variables
|
||||
- Access shortcut which activated command
|
||||
- Fixed closing the application at shutdown on Windows
|
||||
- Fixed some global shortcuts on Windows
|
||||
- Fixed capturing some shortcuts
|
||||
|
||||
v2.5.0
|
||||
- Smarter tab name matching (ignore key hints '&')
|
||||
- Fixed omit passing global shortcuts to widgets
|
||||
- Fixed autostart option on Ubuntu
|
||||
- Fixed window geometry saving and restoring
|
||||
- Fixed reading binary input on Windows
|
||||
- Fixed clearing configuration
|
||||
|
||||
v2.4.9
|
||||
- Added new light theme
|
||||
- Added scriptable function focused() to test main window focus
|
||||
- Customizable shortcuts for tab navigation
|
||||
- Extended item selection
|
||||
- Fixed tab expiration and updating context menu
|
||||
- Fixed passing text to command from action dialog
|
||||
|
||||
v2.4.8
|
||||
- New command to show main window under mouse cursor or at a position with custom size
|
||||
- Hide clipboard content when "application/x-copyq-hidden" is "1"
|
||||
- "Copy next/previous item" command waits for clipboard to be set
|
||||
- Fixed updating window title and tray tool tip on X11
|
||||
- Fixed modifying multiple commands in Command dialog
|
||||
- Fixed implicit date to string conversions
|
||||
|
||||
v2.4.7
|
||||
- Separate dialog for command help
|
||||
- Added scriptable function visible() to check main window visibility
|
||||
- Linux: Install bitmap icons for menus
|
||||
- Linux: Install AppData file
|
||||
- Allow to search for specific MIME types stored in items
|
||||
- Menu items and customizable shortcut for cycling item format
|
||||
- Fixed icon alignment
|
||||
- Fixed moving tabs with Qt 5
|
||||
- Fixed overriding socket file path (Linux and OS X)
|
||||
- Fixed "Paste as Plain Text" command (Windows and OS X)
|
||||
- Fixed tab tree layout and changing icons for tab groups
|
||||
- Fixed URL encoding
|
||||
|
||||
v2.4.6
|
||||
- Fixed crash when removing command
|
||||
- Fixed encryption/decryption selected items
|
||||
- Fixed reading from standard input
|
||||
- GUI fixes for high-DPI displays
|
||||
|
||||
v2.4.5
|
||||
- Option to save/restore history for filtering items
|
||||
- Clipboard changes with unchanged content is ignored
|
||||
- Notify about unsaved changes in command dialog
|
||||
- Use application icons from current icon theme on Linux
|
||||
- Simple error checking for user scripts
|
||||
- Fix blocked system shutdown on Linux/X11
|
||||
|
||||
v2.4.4
|
||||
- Option to choose tab for storing clipboard
|
||||
- Fixed overriding mouse selection (Linux/X11)
|
||||
- Fixed window title updates from user commands
|
||||
- Fixed toggling window visibility with Qt 5
|
||||
- Minor GUI improvements and user command fixes
|
||||
|
||||
v2.4.3
|
||||
- Plugin for tagging items
|
||||
- Plugins can provide script functions and commands
|
||||
- Improved automatic commands execution
|
||||
- Fixed gradients, transparency and other style improvements
|
||||
- Fixed decryption with newer version of GnuPG
|
||||
- Fixes for Qt 5 version
|
||||
|
||||
v2.4.2
|
||||
- Send input data to execute()
|
||||
- Better clipboard encoding guessing
|
||||
- Set tab icon from commands using tabicon()
|
||||
- Fixed window title encoding on Windows
|
||||
- Fixed restoring window geometry
|
||||
- Performance fixes
|
||||
- Various bug and usability fixes
|
||||
- New logo
|
||||
|
||||
v2.4.1
|
||||
- Added scriptable classes File and Dir
|
||||
- Added scriptable function settings() for saving custom user data
|
||||
- Improved dialog() command
|
||||
- Windows: Qt translated strings bundled with application
|
||||
- Fixed %1 in command
|
||||
- Fixed building with tests and Qt5
|
||||
|
||||
v2.4.0
|
||||
- Separate dialog for user commands and global shortcuts
|
||||
- Search for item by row number
|
||||
- Command highlighting
|
||||
- More shortcuts can be mapped on Windows and X11
|
||||
- New "copy" command to copy from current window to clipboard
|
||||
- New "dialog" command to show dialog with custom input fields
|
||||
- Fixed crash on log out on Windows
|
||||
- Fixed clipboard monitoring on Windows
|
||||
- Fixed argument encoding from client on Windows
|
||||
- Fixed log output when printing messages from multiple processes
|
||||
- GUI fixes
|
||||
|
||||
v2.3.0
|
||||
- Support for OS X
|
||||
- Japanese translation
|
||||
- Custom icons for tabs
|
||||
- Show item count next to each tab name (optional)
|
||||
- Added Process Manager for running and finished commands
|
||||
- Scripting improvements
|
||||
- Nicer format for copied user commands
|
||||
- GUI fixes
|
||||
|
||||
v2.2.0
|
||||
- Custom system shortcuts for any user command
|
||||
- Drag'n'drop items to tabs
|
||||
- Options to set position and maximum size for notifications
|
||||
- Option to open windows on same desktop
|
||||
- Weblate service for translations (https://hosted.weblate.org/projects/copyq/master/)
|
||||
- Commands input and output is UTF-8 only (this fixes encoding issues on Windows)
|
||||
- Scripting engine improvements
|
||||
- Various GUI improvements and fixes
|
||||
- Fix main window position in various X11 window managers
|
||||
- Fix crashing with Oxygen GUI style
|
||||
- Fix storing images from clipboard on Windows
|
||||
- Various GUI improvements and fixes
|
||||
|
||||
v2.1.0
|
||||
- French translation
|
||||
- Save/load and copy/paste user commands
|
||||
- Easier way to write longer commands and scripts
|
||||
- Remove formats in clipboard and item content dialogs
|
||||
- Command "toggle" focuses main window if unfocused (instead of closing)
|
||||
- Choose log file and amount of information to log
|
||||
- Lot of bugfixes and GUI improvements
|
||||
|
||||
v2.0.1
|
||||
- Initial OS X support
|
||||
- Configuration moved into installed directory in Windows
|
||||
- Change language in configuration
|
||||
- New tool bar with item actions
|
||||
- Option to apply color theme in tabs, tool bar and menus
|
||||
- Allow to match items using a command
|
||||
- Focus output item of the last executed command
|
||||
- Allow to cancel exit if there are active commands
|
||||
- Removed option to hide menu bar (inconsistent behavior)
|
||||
- Fix showing lock icon in encrypted items
|
||||
|
||||
v2.0.0
|
||||
- Synchronize items with files on disk
|
||||
- Faster tab loading and saving (data format was changed; only backward compatible)
|
||||
- User can limit size of text items
|
||||
- Opening external image editor fixed on Windows
|
||||
- New logo and website
|
||||
- Lot of other fixes
|
||||
|
||||
v1.9.3
|
||||
- Item and tab encryption (using GnuPG)
|
||||
- FakeVim plugin for editing items (Vim editor emulation)
|
||||
- Drag'n'drop items from and to list
|
||||
- Improved appearance for notes
|
||||
- Improved search bar
|
||||
- New GUI for application and system-wide shortcuts
|
||||
- Option to unload tabs after an interval
|
||||
- Fixed item sizes and disabling font anti-aliasing
|
||||
- Major bug fixes (mainly for Windows) and performance improvements
|
||||
|
||||
v1.9.2
|
||||
- Better performance
|
||||
- GUI improvements and bugfixes
|
||||
|
||||
v1.9.1
|
||||
- Notifications -- customizable theme, timeout and position on screen
|
||||
- Optional notification for new clipboard content
|
||||
- Autostart option on Linux
|
||||
- Reset empty clipboard to previous content
|
||||
- More user-friendly item editor
|
||||
- Optional font antialiasing
|
||||
- Changed layout of configuration dialog
|
||||
- Other fixes
|
||||
|
||||
v1.9.0
|
||||
- User notes
|
||||
- Improved appearance settings with some example themes
|
||||
- Tree view for tabs with groups
|
||||
- Sessions, i.e. run multiple independent instances
|
||||
- Lot of GUI improvements
|
||||
- Compatibility with Qt5
|
||||
- Bugfixes (crashing on Unity, icon colors etc.)
|
||||
|
||||
v1.8.3
|
||||
- Options to hide tab bar and main menu
|
||||
- Automatic paste works with more applications under Linux/X11
|
||||
- Multi-monitor support
|
||||
- Lot of GUI fixes and improvements
|
||||
|
||||
v1.8.2
|
||||
- Added shortcut to paste current and copy next/previous item
|
||||
- Bugfixes (paste to correct window, show tray menu on Unity, GUI and usability fixes)
|
||||
|
||||
v1.8.1
|
||||
- Spanish translation
|
||||
- Option and system-wide shortcuts to temporarily disable clipboard storing
|
||||
- Option for main window transparency
|
||||
- Custom action on item activation
|
||||
- Various GUI improvements and bugfixes
|
||||
|
||||
v1.8.0
|
||||
- New shortcuts: "Next/previous item to clipboard", "Paste as plain text"
|
||||
- Show clipboard content in main window title and tray tooltip
|
||||
- New options for commands (transform current item, close main window)
|
||||
- GUI enhancements, faster application start with many tabs and items, lot of bugfixes
|
||||
|
||||
v1.7.5
|
||||
- User-settable editor for images
|
||||
- Command-line fixes for Windows
|
||||
- Commands for items of specified format (MIME type)
|
||||
- Tray menu fixes
|
||||
|
||||
v1.7.4
|
||||
- Improved automatic paste from tray
|
||||
|
||||
v1.7.3
|
||||
- Paste immediately after choosing tray item
|
||||
- German translation
|
||||
- Support for system-wide shortcuts on Qt 5
|
||||
|
||||
v1.7.2
|
||||
- Clipboard content visible in tray tooltip
|
||||
|
||||
v1.7.1
|
||||
- Bugfixes for text encoding
|
||||
|
||||
v1.7.0
|
||||
- Plugins for saving and displaying clipboard content
|
||||
- Bugfixes (lot of refactoring and tests happened)
|
||||
|
||||
v1.6.3
|
||||
- Some important bugfixes
|
||||
|
||||
v1.6.2
|
||||
- Dialog for viewing item content
|
||||
- Improved tray menu
|
||||
- Minor GUI updates
|
||||
|
||||
v1.6.1
|
||||
- Configurable tray menu
|
||||
- Lot of fixes in GUI and bugfixes
|
||||
|
||||
v1.6.0
|
||||
- Highlight text and copy text in items
|
||||
- Interactive web view
|
||||
- Commands for any MIME type
|
||||
- e.g. it's possible to create QR Code image from an URL and save it in list
|
||||
- Pipe commands using '|' character
|
||||
|
||||
v1.5.0
|
||||
- Option to use WebKit to render HTML
|
||||
- Wrap text with long lines
|
||||
- Faster list rendering
|
||||
- Icons from FontAwesome
|
||||
- Desktop icon on Linux
|
||||
|
||||
v1.4.1
|
||||
- Support for other languages -- right now supports only English and Czech (any help is welcome)
|
||||
- New "insert" command
|
||||
- More safe item saving
|
||||
|
||||
v1.4.0
|
||||
- lot of GUI Improvements, faster interaction
|
||||
- Automatic commands for matched windows (only on Linux and Windows)
|
||||
|
||||
v1.3.3
|
||||
- GUI Improvements
|
||||
- New system-wide shortcuts
|
||||
- Item editing improved
|
||||
|
||||
v1.3.2
|
||||
- Drag'n'Drop to clipboard
|
||||
- "Always on Top" option
|
||||
- Change tab bar position
|
||||
- Fix parsing arguments
|
||||
|
||||
v1.3.1
|
||||
- GUI improvements
|
||||
- Mode for Vi navigation (h, j, k, l keys for movement)
|
||||
- Better performance
|
||||
|
||||
v1.3.0
|
||||
- Import/export items to/from a file (not compatible with older saved format)
|
||||
- Use scripts to handle item history
|
||||
- Improved performance
|
||||
|
||||
v1.2.5
|
||||
- Save/load items to/from a file
|
||||
- Sort selected items
|
||||
- Easier tab browsing (left/right arrow keys)
|
||||
- GUI improvements
|
||||
- More shortcut combinations work on Linux
|
||||
|
||||
v1.2.4
|
||||
- Improved commands
|
||||
- Fixed and faster scrolling
|
||||
- Better tab manipulation
|
||||
|
||||
v1.2.3
|
||||
- Bugfixes and major clean-up
|
||||
|
||||
v1.2.2
|
||||
- Performance improved
|
||||
|
||||
v1.2.1
|
||||
- Save items from commands in other tabs
|
||||
- Missing icons in Windows version
|
||||
|
||||
v1.2.0
|
||||
- Appearance settings
|
||||
- Tab manipulation from command line
|
||||
- Copy/paste items from/to tabs
|
||||
- Faster searching
|
||||
|
||||
v1.1.0
|
||||
- Better performance
|
||||
- New configuration options
|
||||
- Improved command line
|
||||
|
||||
v1.0.2
|
||||
- Improved Windows compatibility
|
||||
- Global shortcuts
|
||||
- Automatic commands
|
||||
|
||||
v1.0.1
|
||||
- Compatibility with different platforms
|
||||
|
@ -0,0 +1,202 @@
|
||||
cmake_minimum_required(VERSION 2.8.6)
|
||||
project(copyq)
|
||||
|
||||
# C++11
|
||||
if (${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} GREATER 3.1)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set(COPYQ_DEBUG ON)
|
||||
add_definitions( -DCOPYQ_DEBUG )
|
||||
endif()
|
||||
|
||||
OPTION(PEDANTIC "Enable all compiler warnings" OFF)
|
||||
|
||||
# Options (cmake -LH)
|
||||
OPTION(WITH_QT5 "Use Qt 5 (disable to use Qt 4 instead)" ON)
|
||||
OPTION(WITH_TESTS "Run test cases from command line" ${COPYQ_DEBUG})
|
||||
OPTION(WITH_PLUGINS "Compile plugins" ON)
|
||||
# Linux-specific options
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
set(PLUGIN_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_MODULE_PREFIX}/copyq/plugins" CACHE PATH "Install path for plugins")
|
||||
set(ICON_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps" CACHE PATH "Install path for icons")
|
||||
set(ICON_INSTALL_PREFIX_TEMPLATE "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/%SIZE%/apps" CACHE PATH "Install path for icons (%SIZE% is icon size)")
|
||||
set(THEME_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/share/copyq/themes" CACHE PATH "Install path for themes")
|
||||
set(DESKTOP_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/share/applications" CACHE PATH "Install path for \"copyq.desktop\"")
|
||||
set(APPDATA_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/share/appdata" CACHE PATH "Install path for \"copyq.appdata.xml\"")
|
||||
set(TRANSLATION_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/share/copyq/translations" CACHE PATH "Install path for translations")
|
||||
endif()
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
if (WITH_QT5)
|
||||
cmake_minimum_required(VERSION 2.8.8)
|
||||
find_package(Qt5Widgets)
|
||||
if (NOT Qt5Widgets_FOUND)
|
||||
message(FATAL_ERROR "Qt 5 is unavailable. To compile with Qt 4 use -DWITH_QT5=OFF.")
|
||||
endif()
|
||||
message(STATUS "Building with Qt 5.")
|
||||
else()
|
||||
find_package(Qt4)
|
||||
if (NOT QT4_FOUND)
|
||||
# Try different executable name.
|
||||
set(QT_QMAKE_EXECUTABLE "qmake-qt4")
|
||||
find_package(Qt4)
|
||||
if (NOT QT4_FOUND)
|
||||
message(FATAL_ERROR "Qt 4 is unavailable. To compile with Qt 5 use -DWITH_QT5=ON.")
|
||||
endif()
|
||||
endif()
|
||||
message(STATUS "Building with Qt 4.")
|
||||
endif()
|
||||
|
||||
set(copyq_ICON_PREFIX src/images/icon)
|
||||
set(copyq_ICON_NORMAL src/images/icon.svg)
|
||||
set(copyq_ICON_BUSY src/images/icon-running.svg)
|
||||
set(copyq_DESKTOP shared/copyq.desktop)
|
||||
set(copyq_APPDATA shared/copyq.appdata.xml)
|
||||
|
||||
# Be more strict while compiling debugging version
|
||||
if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-long-long")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG
|
||||
"${CMAKE_CXX_FLAGS_DEBUG} -Wextra -Wall -pedantic -Wfloat-equal -Woverloaded-virtual -Wundef")
|
||||
endif()
|
||||
|
||||
if (PEDANTIC)
|
||||
if (CMAKE_COMPILER_IS_GNUCXX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra -Wall \
|
||||
-Wsuggest-override \
|
||||
-Wlogical-op \
|
||||
-Wnoexcept \
|
||||
-Wstrict-null-sentinel \
|
||||
")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weverything \
|
||||
-Winconsistent-missing-override \
|
||||
")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-Wno-c++98-compat \
|
||||
-Wno-c++98-compat-pedantic \
|
||||
-Wno-shadow-field-in-constructor \
|
||||
-Wno-weak-vtables \
|
||||
-Wno-disabled-macro-expansion \
|
||||
-fcomment-block-commands=retval \
|
||||
")
|
||||
|
||||
# Disable errors from moc-generated files.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-Wno-undefined-reinterpret-cast \
|
||||
-Wno-missing-prototypes \
|
||||
")
|
||||
|
||||
# Disable errors from qrc-generated files.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-Wno-exit-time-destructors \
|
||||
-Wno-global-constructors \
|
||||
")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS
|
||||
"${CMAKE_CXX_FLAGS} -pedantic -Werror \
|
||||
-Wcast-align \
|
||||
-Wcast-qual \
|
||||
-Wctor-dtor-privacy \
|
||||
-Wdisabled-optimization \
|
||||
-Wformat=2 \
|
||||
-Winit-self \
|
||||
-Wmissing-declarations \
|
||||
-Wmissing-include-dirs \
|
||||
-Wold-style-cast \
|
||||
-Woverloaded-virtual \
|
||||
-Wredundant-decls \
|
||||
-Wstrict-overflow=4 \
|
||||
-Wundef \
|
||||
")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-Wno-padded \
|
||||
-Wno-switch-enum \
|
||||
")
|
||||
|
||||
# Disable Q_OBJECT macro warnings.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-Wno-unused-member-function \
|
||||
")
|
||||
endif()
|
||||
|
||||
if(WITH_TESTS)
|
||||
message(STATUS "Building with tests.")
|
||||
|
||||
add_definitions( -DHAS_TESTS )
|
||||
|
||||
if (WITH_QT5)
|
||||
list(APPEND copyq_Qt5_Modules Test)
|
||||
else()
|
||||
set(QT_USE_QTTEST TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Get application version.
|
||||
if (EXISTS "version.txt")
|
||||
file(STRINGS "version.txt" copyq_version)
|
||||
endif()
|
||||
|
||||
if (NOT copyq_version)
|
||||
find_package(Git)
|
||||
if(GIT_FOUND)
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}" describe
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
RESULT_VARIABLE copyq_git_describe_result
|
||||
OUTPUT_VARIABLE copyq_git_describe_output
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
if(copyq_git_describe_result EQUAL 0)
|
||||
set(copyq_version "${copyq_git_describe_output}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (copyq_version)
|
||||
message(STATUS "Building CopyQ version ${copyq_version}.")
|
||||
add_definitions( -DCOPYQ_VERSION="${copyq_version}" )
|
||||
endif()
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
install(FILES ${copyq_ICON_NORMAL} DESTINATION ${ICON_INSTALL_PREFIX} RENAME copyq-normal.svg)
|
||||
install(FILES ${copyq_ICON_BUSY} DESTINATION ${ICON_INSTALL_PREFIX} RENAME copyq-busy.svg)
|
||||
install(FILES ${copyq_DESKTOP} DESTINATION ${DESKTOP_INSTALL_PREFIX})
|
||||
install(FILES ${copyq_APPDATA} DESTINATION ${APPDATA_INSTALL_PREFIX})
|
||||
|
||||
foreach (copyq_ICON_EXTENT 16 22 24 32 48 64 128)
|
||||
set(copyq_ICON_SIZE "${copyq_ICON_EXTENT}x${copyq_ICON_EXTENT}")
|
||||
string(REPLACE "%SIZE%" "${copyq_ICON_SIZE}" copyq_ICON_TARGET_PREFIX "${ICON_INSTALL_PREFIX_TEMPLATE}")
|
||||
foreach (copyq_ICON_TYPE "" "-normal" "-busy")
|
||||
install(FILES "${copyq_ICON_PREFIX}${copyq_ICON_TYPE}_${copyq_ICON_SIZE}.png" DESTINATION "${copyq_ICON_TARGET_PREFIX}" RENAME "copyq${copyq_ICON_TYPE}.png")
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
set(copyq_THEME_INSTALL_PREFIX ${THEME_INSTALL_PREFIX})
|
||||
file(GLOB copyq_THEMES shared/themes/*.ini)
|
||||
install(FILES ${copyq_THEMES} DESTINATION ${THEME_INSTALL_PREFIX})
|
||||
|
||||
add_definitions( -DCOPYQ_ICON_PREFIX="${ICON_INSTALL_PREFIX}/copyq" )
|
||||
add_definitions( -DCOPYQ_THEME_PREFIX="${THEME_INSTALL_PREFIX}" )
|
||||
add_definitions( -DCOPYQ_PLUGIN_PREFIX="${PLUGIN_INSTALL_PREFIX}" )
|
||||
add_definitions( -DCOPYQ_DESKTOP_PREFIX="${DESKTOP_INSTALL_PREFIX}" )
|
||||
add_definitions( -DCOPYQ_TRANSLATION_PREFIX="${TRANSLATION_INSTALL_PREFIX}" )
|
||||
endif()
|
||||
|
||||
add_definitions( -DQT_NO_CAST_TO_ASCII )
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
if (WITH_PLUGINS)
|
||||
add_subdirectory(plugins)
|
||||
endif()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
https://github.com/hluk/CopyQ/wiki/Development
|
@ -0,0 +1,30 @@
|
||||
Steps to build and install
|
||||
==========================
|
||||
|
||||
To build and install the application CMake is required (http://www.cmake.org/).
|
||||
|
||||
On Ubuntu you'll need packages libqt4-dev, cmake, libxfixes-dev,
|
||||
libxtst-dev (optional; auto-paste into some applications),
|
||||
libqtwebkit-dev (optional; advanced HTML rendering).
|
||||
|
||||
Build with following commands:
|
||||
|
||||
cmake .
|
||||
make
|
||||
|
||||
To install run (sudo is required for root privileges on Linux):
|
||||
|
||||
sudo make install
|
||||
|
||||
|
||||
Building and Packaging for OS X
|
||||
-------------------------------
|
||||
|
||||
To build and install on OS X, you will need `qmake` version
|
||||
5.2 or greater. Build with the following commands:
|
||||
|
||||
~/Qt/5.2.0/clang_64/bin/qmake CONFIG+=release WITH_WEBKIT=1
|
||||
make copyq.app
|
||||
|
||||
This will produce a self-contained application bundle `copyq.app`
|
||||
which can then be copied or moved into `/Applications`.
|
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
@ -0,0 +1,210 @@
|
||||
# CopyQ
|
||||
|
||||
[![Translation Status](https://hosted.weblate.org/widgets/copyq/-/svg-badge.svg)](https://hosted.weblate.org/engage/copyq/?utm_source=widget)
|
||||
[![Build Status](https://travis-ci.org/hluk/CopyQ.svg?branch=master)](https://travis-ci.org/hluk/CopyQ)
|
||||
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/hluk/copyq?branch=master&svg=true)](https://ci.appveyor.com/project/hluk/copyq)
|
||||
[![Coverage Status](https://coveralls.io/repos/hluk/CopyQ/badge.svg?branch=master)](https://coveralls.io/r/hluk/CopyQ?branch=master)
|
||||
|
||||
CopyQ is advanced clipboard manager with editing and scripting features.
|
||||
- [Downloads](https://github.com/hluk/CopyQ/releases)
|
||||
- [Web Site](https://hluk.github.io/CopyQ/)
|
||||
- [Wiki](https://github.com/hluk/CopyQ/wiki)
|
||||
- [Mailing List](https://groups.google.com/group/copyq)
|
||||
- [Bug Reports](https://github.com/hluk/CopyQ/issues)
|
||||
- [Donate](https://www.bountysource.com/teams/copyq)
|
||||
- [Scripting Reference](https://github.com/hluk/CopyQ/blob/master/src/scriptable/README.md)
|
||||
|
||||
## Overview
|
||||
|
||||
CopyQ monitors system clipboard and saves its content in customized tabs.
|
||||
Saved clipboard can be later copied and pasted directly into any application.
|
||||
|
||||
Items can be:
|
||||
|
||||
* edited with internal editor or with preferred text editor,
|
||||
* moved to other tabs,
|
||||
* drag'n'dropped to applications,
|
||||
* marked with tag or a note,
|
||||
* passed to or changed by custom commands,
|
||||
* or simply removed.
|
||||
|
||||
## Features
|
||||
|
||||
* Support for Linux, Windows and OS X 10.9+
|
||||
* Store text, HTML, images or any other custom formats
|
||||
* Quickly browse and filter items in clipboard history
|
||||
* Sort, create, edit, remove, copy/paste, drag'n'drop items in tabs
|
||||
* Add notes or tags to items
|
||||
* System-wide shortcuts with customizable commands
|
||||
* Paste items with shortcut or from tray or main window
|
||||
* Fully customizable appearance
|
||||
* Advanced command-line interface and scripting
|
||||
* Ignore clipboard copied from some windows or containing some text
|
||||
* Support for simple Vim-like editor and shortcuts
|
||||
* Many more features
|
||||
|
||||
## Install and Run
|
||||
|
||||
To install CopyQ, use the binary package or installer provided for your system. For system-specific information, please see below. For unlisted systems, please follow the instructions in
|
||||
[INSTALL](https://github.com/hluk/CopyQ/blob/master/INSTALL) to build the
|
||||
application.
|
||||
|
||||
### Windows
|
||||
|
||||
On Windows you can install [Chocolatey package](https://chocolatey.org/packages/copyq).
|
||||
|
||||
### Ubuntu
|
||||
|
||||
Install and keep CopyQ always up to date by running the following three commands from the terminal:
|
||||
|
||||
```bash
|
||||
$ sudo add-apt-repository ppa:hluk/copyq
|
||||
$ sudo apt update
|
||||
$ sudo apt install copyq
|
||||
```
|
||||
|
||||
### OS X
|
||||
|
||||
On OS X you can use [Homebrew](https://brew.sh/) to install the app.
|
||||
|
||||
```bash
|
||||
brew cask install copyq
|
||||
```
|
||||
|
||||
## Using the App
|
||||
|
||||
To start the application double-click the program icon or run `copyq`.
|
||||
|
||||
The list with clipboard history is accessible by clicking on system tray icon
|
||||
or running `copyq toggle`.
|
||||
|
||||
Copying text or image to clipboard will create new item in the list.
|
||||
|
||||
Selected items can be:
|
||||
* edited (`F2`),
|
||||
* removed (`Delete`),
|
||||
* sorted (`Ctrl+Shift+S`, `Ctrl+Shift+R`),
|
||||
* moved around (with mouse or `Ctrl+Up/Down`) or
|
||||
* copied back to clipboard (`Enter`, `Ctrl+V`).
|
||||
|
||||
All items will be restored when application is started next time.
|
||||
|
||||
To exit the application select Exit from tray menu or press Ctrl-Q keys in the
|
||||
application window.
|
||||
|
||||
Read more:
|
||||
- [Basic Usage](https://github.com/hluk/CopyQ/wiki/Basic-Usage)
|
||||
- [Keyboard](https://github.com/hluk/CopyQ/wiki/Keyboard)
|
||||
|
||||
### Adding Funcionality
|
||||
|
||||
To create custom action that can be executed
|
||||
from menu, with shortcut or when clipboard changes:
|
||||
- go to Command dialog (`F6` shortcut),
|
||||
- click Add button and select predefined command or create new one,
|
||||
- optionally change the command details (shortcut, name),
|
||||
- click OK to save the command.
|
||||
|
||||
One of very useful predefined commands there is "Show/hide main window".
|
||||
|
||||
Read more:
|
||||
- [Writing Commands](https://github.com/hluk/CopyQ/wiki/Writing-Commands-and-Adding-Functionality)
|
||||
- [CopyQ Commands Repository](https://github.com/hluk/copyq-commands)
|
||||
|
||||
### Command Line
|
||||
|
||||
CopyQ has powerful command line and scripting interface.
|
||||
|
||||
Note: The main application must be running to be able to issue commands using
|
||||
command line.
|
||||
|
||||
Print help for some useful command line arguments:
|
||||
|
||||
copyq --help
|
||||
copyq --help add
|
||||
|
||||
Insert some texts to the history:
|
||||
|
||||
copyq add "first item" "second item" "third item"
|
||||
|
||||
Print content of the first three items:
|
||||
|
||||
copyq read 0 1 2
|
||||
copyq separator "," read 0 1 2
|
||||
|
||||
Show current clipboard content:
|
||||
|
||||
copyq clipboard
|
||||
copyq clipboard text/html
|
||||
copyq clipboard \? # lists formats in clipboard
|
||||
|
||||
Copy text to the clipboard:
|
||||
|
||||
copyq copy "Some Text"
|
||||
|
||||
Load file content into clipboard:
|
||||
|
||||
copyq copy - < file.txt
|
||||
copyq copy text/html < index.html
|
||||
copyq copy image/jpeg - < image.jpg
|
||||
|
||||
Create an image items:
|
||||
|
||||
copyq write image/gif - < image.gif
|
||||
copyq write image/svg - < image.svg
|
||||
|
||||
Read more:
|
||||
- [Scripting](https://github.com/hluk/CopyQ/wiki/Scripting)
|
||||
- [Scripting Reference](https://github.com/hluk/CopyQ/blob/master/src/scriptable/README.md)
|
||||
|
||||
## Build from Source Code
|
||||
|
||||
To build the application from source code, first install the required dependencies:
|
||||
- [Git](https://git-scm.com/)
|
||||
- [CMake](https://cmake.org/download/)
|
||||
- [Qt](https://download.qt.io/archive/qt/)
|
||||
- Optionally on Linux/X11: development files and libraries for [Xtst](https://t2-project.org/packages/libxtst.html) and [Xfixes](https://www.x.org/archive/X11R7.5/doc/man/man3/Xfixes.3.html)
|
||||
- Optionally [QtWebKit](https://trac.webkit.org/wiki/QtWebKit) (more advanced HTML rendering)
|
||||
|
||||
### Ubuntu
|
||||
|
||||
#### Install Dependencies
|
||||
|
||||
```bash
|
||||
sudo apt install \
|
||||
git cmake \
|
||||
qtbase5-private-dev \
|
||||
qtscript5-dev \
|
||||
qttools5-dev \
|
||||
qttools5-dev-tools \
|
||||
libqt5svg5-dev \
|
||||
libxfixes-dev \
|
||||
libxtst-dev \
|
||||
libqt5svg5
|
||||
```
|
||||
|
||||
#### Build the App
|
||||
|
||||
Change install prefix if needed:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/hluk/CopyQ.git
|
||||
cd CopyQ
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr/local .
|
||||
make
|
||||
```
|
||||
|
||||
#### Install the App
|
||||
|
||||
```bash
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
You can help translate the application (click the banner below)
|
||||
or help [fix issues and implement new features](https://github.com/hluk/CopyQ/issues).
|
||||
|
||||
[![Translations](https://hosted.weblate.org/widgets/copyq/-/287x66-white.png)](https://hosted.weblate.org/engage/copyq/?utm_source=widget)
|
||||
|
||||
See also [Development](https://github.com/hluk/CopyQ/wiki/Development).
|
@ -0,0 +1,46 @@
|
||||
# Configuration file for AppVeyor CI
|
||||
configuration: Release
|
||||
|
||||
cache:
|
||||
- build
|
||||
|
||||
environment:
|
||||
VCINSTALLDIR: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\
|
||||
|
||||
matrix:
|
||||
- QTDIR: C:\Qt\5.7\mingw53_32
|
||||
CMAKE_GENERATOR: MinGW Makefiles
|
||||
MINGW_PATH: C:\Qt\Tools\mingw530_32
|
||||
|
||||
- QTDIR: C:\Qt\5.7\msvc2015
|
||||
CMAKE_GENERATOR: Visual Studio 14 2015
|
||||
VC_VARS_ARCH: x86
|
||||
|
||||
- QTDIR: C:\Qt\5.7\msvc2015_64
|
||||
CMAKE_GENERATOR: Visual Studio 14 2015 Win64
|
||||
VC_VARS_ARCH: amd64
|
||||
|
||||
# Parameters for default build commands (build_script is used instead).
|
||||
build:
|
||||
|
||||
install:
|
||||
- utils\appveyor\install.bat
|
||||
|
||||
before_build:
|
||||
- utils\appveyor\before_build.bat
|
||||
|
||||
build_script:
|
||||
- utils\appveyor\build_script.bat
|
||||
|
||||
after_build:
|
||||
- utils\appveyor\after_build.bat
|
||||
|
||||
artifacts:
|
||||
- path: 'copyq*.zip'
|
||||
name: CopyQ Portable
|
||||
|
||||
- path: 'copyq-*-setup.exe'
|
||||
name: CopyQ Setup
|
||||
|
||||
matrix:
|
||||
fast_finish: true # set this flag to immediately finish build once one of the jobs fails.
|
@ -0,0 +1,9 @@
|
||||
CONFIG += c++11
|
||||
|
||||
macx {
|
||||
QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.9
|
||||
QMAKE_MAC_SDK = macosx # work around QTBUG-41238
|
||||
|
||||
# Only Intel binaries are accepted so force this
|
||||
CONFIG += x86
|
||||
}
|
Binary file not shown.
@ -0,0 +1,22 @@
|
||||
%lang(ar) /usr/share/copyq/locale/copyq_ar.qm
|
||||
%lang(cs) /usr/share/copyq/locale/copyq_cs.qm
|
||||
%lang(da) /usr/share/copyq/locale/copyq_da.qm
|
||||
%lang(de) /usr/share/copyq/locale/copyq_de.qm
|
||||
%lang(es) /usr/share/copyq/locale/copyq_es.qm
|
||||
%lang(fr) /usr/share/copyq/locale/copyq_fr.qm
|
||||
%lang(hu) /usr/share/copyq/locale/copyq_hu.qm
|
||||
%lang(it) /usr/share/copyq/locale/copyq_it.qm
|
||||
%lang(ja) /usr/share/copyq/locale/copyq_ja.qm
|
||||
%lang(lt) /usr/share/copyq/locale/copyq_lt.qm
|
||||
%lang(nb) /usr/share/copyq/locale/copyq_nb.qm
|
||||
%lang(nl) /usr/share/copyq/locale/copyq_nl.qm
|
||||
%lang(pl) /usr/share/copyq/locale/copyq_pl.qm
|
||||
%lang(pt_BR) /usr/share/copyq/locale/copyq_pt_BR.qm
|
||||
%lang(pt_PT) /usr/share/copyq/locale/copyq_pt_PT.qm
|
||||
%lang(ru) /usr/share/copyq/locale/copyq_ru.qm
|
||||
%lang(sk) /usr/share/copyq/locale/copyq_sk.qm
|
||||
%lang(sv) /usr/share/copyq/locale/copyq_sv.qm
|
||||
%lang(tr) /usr/share/copyq/locale/copyq_tr.qm
|
||||
%lang(uk) /usr/share/copyq/locale/copyq_uk.qm
|
||||
%lang(zh_CN) /usr/share/copyq/locale/copyq_zh_CN.qm
|
||||
%lang(zh_TW) /usr/share/copyq/locale/copyq_zh_TW.qm
|
@ -0,0 +1,69 @@
|
||||
include("./common.pri")
|
||||
|
||||
TEMPLATE = subdirs
|
||||
|
||||
# generate cache file for build
|
||||
cache()
|
||||
|
||||
DEFINES += QT_NO_CAST_TO_ASCII
|
||||
SUBDIRS += src \
|
||||
plugins
|
||||
TRANSLATIONS = \
|
||||
translations/copyq_ar.ts \
|
||||
translations/copyq_cs.ts \
|
||||
translations/copyq_da.ts \
|
||||
translations/copyq_de.ts \
|
||||
translations/copyq_es.ts \
|
||||
translations/copyq_fr.ts \
|
||||
translations/copyq_hu.ts \
|
||||
translations/copyq_it.ts \
|
||||
translations/copyq_ja.ts \
|
||||
translations/copyq_lt.ts \
|
||||
translations/copyq_nb.ts \
|
||||
translations/copyq_nl.ts \
|
||||
translations/copyq_pl.ts \
|
||||
translations/copyq_pt_PT.ts \
|
||||
translations/copyq_pt_BR.ts \
|
||||
translations/copyq_ru.ts \
|
||||
translations/copyq_sk.ts \
|
||||
translations/copyq_sv.ts \
|
||||
translations/copyq_tr.ts \
|
||||
translations/copyq_uk.ts \
|
||||
translations/copyq_zh_CN.ts \
|
||||
translations/copyq_zh_TW.ts
|
||||
|
||||
macx {
|
||||
# Package the CopyQ plugins into the app bundle
|
||||
package_plugins.commands = \
|
||||
mkdir -p copyq.app/Contents/PlugIns/copyq/ ; \
|
||||
cp plugins/*.dylib copyq.app/Contents/PlugIns/copyq/
|
||||
package_plugins.depends = sub-plugins sub-src
|
||||
QMAKE_EXTRA_TARGETS += package_plugins
|
||||
|
||||
# Package the Qt frameworks into the app bundle
|
||||
package_frameworks.commands = \
|
||||
test -e copyq.app/Contents/Frameworks/QtCore.framework \
|
||||
|| $$dirname(QMAKE_QMAKE)/macdeployqt copyq.app
|
||||
package_frameworks.target = copyq.app/Contents/Frameworks/QtCore.framework
|
||||
package_frameworks.depends = sub-src sub-plugins package_plugins
|
||||
QMAKE_EXTRA_TARGETS += package_frameworks
|
||||
|
||||
# Package the translations
|
||||
package_translations.commands = \
|
||||
$$dirname(QMAKE_QMAKE)/lrelease $$_PRO_FILE_PWD_/copyq.pro && \
|
||||
mkdir -p copyq.app/Contents/Resources/translations && \
|
||||
cp $$_PRO_FILE_PWD_/translations/*.qm copyq.app/Contents/Resources/translations
|
||||
QMAKE_EXTRA_TARGETS += package_translations
|
||||
|
||||
# Package the themes
|
||||
package_themes.commands = \
|
||||
mkdir -p copyq.app/Contents/Resources/themes && \
|
||||
cp $$_PRO_FILE_PWD_/shared/themes/*.ini copyq.app/Contents/Resources/themes
|
||||
QMAKE_EXTRA_TARGETS += package_themes
|
||||
|
||||
# Rename to CopyQ.app to make it look better
|
||||
bundle_mac.depends = package_frameworks package_plugins package_translations package_themes
|
||||
bundle_mac.target = CopyQ.app
|
||||
bundle_mac.commands = mv copyq.app CopyQ.app
|
||||
QMAKE_EXTRA_TARGETS += bundle_mac
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
copyq (2.9.0~xenial) xenial; urgency=medium
|
||||
|
||||
* v2.9.0
|
||||
|
||||
-- Lukas Holecek <hluk@email.cz> Fri, 10 Mar 2017 10:10:00 +0200
|
@ -0,0 +1 @@
|
||||
9
|
@ -0,0 +1,45 @@
|
||||
Source: copyq
|
||||
Section: misc
|
||||
Priority: optional
|
||||
Maintainer: Lukas Holecek <hluk@email.cz>
|
||||
Build-Depends: debhelper (>= 9), cmake
|
||||
,qtbase5-private-dev
|
||||
,qtscript5-dev
|
||||
,qttools5-dev
|
||||
,qttools5-dev-tools
|
||||
,libqt5svg5-dev
|
||||
,libxfixes-dev
|
||||
,libxtst-dev
|
||||
Standards-Version: 3.9.7
|
||||
Homepage: https://hluk.github.io/CopyQ/
|
||||
Vcs-Browser: https://github.com/hluk/CopyQ
|
||||
Vcs-Git: https://github.com/hluk/CopyQ.git
|
||||
|
||||
Package: copyq
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}, libqt5svg5
|
||||
Description: Advanced clipboard manager with editing and scripting features
|
||||
CopyQ monitors system clipboard and saves its content in customized tabs.
|
||||
Saved clipboard can be later copied and pasted directly into any application.
|
||||
.
|
||||
Items can be:
|
||||
* edited with internal editor or with preferred text editor,
|
||||
* moved to other tabs,
|
||||
* drag'n'dropped to applications,
|
||||
* marked with tag or a note,
|
||||
* passed to or changed by custom commands,
|
||||
* or simply removed.
|
||||
.
|
||||
Features:
|
||||
* Support for Linux, Windows and OS X 10.9+
|
||||
* Store text, HTML, images or any other custom formats
|
||||
* Quickly browse and filter items in clipboard history
|
||||
* Sort, create, edit, remove, copy/paste, drag'n'drop items in tabs
|
||||
* Add notes or tags to items
|
||||
* System-wide shortcuts with customizable commands
|
||||
* Paste items with shortcut or from tray or main window
|
||||
* Fully customizable appearance
|
||||
* Advanced command-line interface and scripting
|
||||
* Ignore clipboard copied from some windows or containing some text
|
||||
* Support for simple Vim-like editor and shortcuts
|
||||
* Many more features
|
@ -0,0 +1,41 @@
|
||||
Source: copyq
|
||||
Section: misc
|
||||
Priority: optional
|
||||
Maintainer: Lukas Holecek <hluk@email.cz>
|
||||
Build-Depends: debhelper (>= 9), cmake
|
||||
, libqt4-dev
|
||||
, libxfixes-dev
|
||||
, libxtst-dev
|
||||
Standards-Version: 3.9.7
|
||||
Homepage: https://hluk.github.io/CopyQ/
|
||||
Vcs-Browser: https://github.com/hluk/CopyQ
|
||||
Vcs-Git: https://github.com/hluk/CopyQ.git
|
||||
|
||||
Package: copyq
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}, libqt4-xml, libqt4-network, libqt4-script, libqt4-svg
|
||||
Description: Advanced clipboard manager with editing and scripting features
|
||||
CopyQ monitors system clipboard and saves its content in customized tabs.
|
||||
Saved clipboard can be later copied and pasted directly into any application.
|
||||
.
|
||||
Items can be:
|
||||
* edited with internal editor or with preferred text editor,
|
||||
* moved to other tabs,
|
||||
* drag'n'dropped to applications,
|
||||
* marked with tag or a note,
|
||||
* passed to or changed by custom commands,
|
||||
* or simply removed.
|
||||
.
|
||||
Features:
|
||||
* Support for Linux, Windows and OS X 10.9+
|
||||
* Store text, HTML, images or any other custom formats
|
||||
* Quickly browse and filter items in clipboard history
|
||||
* Sort, create, edit, remove, copy/paste, drag'n'drop items in tabs
|
||||
* Add notes or tags to items
|
||||
* System-wide shortcuts with customizable commands
|
||||
* Paste items with shortcut or from tray or main window
|
||||
* Fully customizable appearance
|
||||
* Advanced command-line interface and scripting
|
||||
* Ignore clipboard copied from some windows or containing some text
|
||||
* Support for simple Vim-like editor and shortcuts
|
||||
* Many more features
|
@ -0,0 +1,124 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: CopyQ
|
||||
Upstream-Contact: Lukas Holecek <hluk@email.cz>
|
||||
Source: https://github.com/hluk/CopyQ
|
||||
Files-Excluded: src/images/*.ttf
|
||||
|
||||
Files: *
|
||||
Copyright: 2009-2017 Lukas Holecek <hluk@email.cz>
|
||||
License: GPL-3+
|
||||
Comment:
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
Files: src/images/fontawesome-webfont.ttf
|
||||
Copyright: Font Awesome by Dave Gandy - http://fontawesome.io
|
||||
License: SIL OFL 1.1
|
||||
URL: http://scripts.sil.org/OFL
|
||||
|
||||
Files:
|
||||
src/gui/execmenu.*
|
||||
src/gui/fancylineedit.*
|
||||
src/gui/filterlineedit.*
|
||||
Copyright: 2014 Digia Plc and/or its subsidiary(-ies).
|
||||
License: LGPL-2.1 or LGPL-3
|
||||
Comment:
|
||||
In addition, as a special exception, Digia gives you certain additional
|
||||
rights. These rights are described in the Digia Qt LGPL Exception
|
||||
version 1.1.
|
||||
.
|
||||
--------
|
||||
Digia Qt LGPL Exception version 1.1
|
||||
.
|
||||
As a special exception to the GNU Lesser General Public License version
|
||||
2.1, the object code form of a "work that uses the Library" may
|
||||
incorporate material from a header file that is part of the Library. You
|
||||
may distribute such object code under terms of your choice, provided that
|
||||
the incorporated material (i) does not exceed more than 5% of the total
|
||||
size of the Library; and (ii) is limited to numerical parameters, data
|
||||
structure layouts, accessors, macros, inline functions and templates.
|
||||
--------
|
||||
http://doc.qt.io/qt-5/lgpl.html
|
||||
|
||||
Files: qxt/*
|
||||
Copyright: 2006-2011, the LibQxt project.
|
||||
License: BSD-3-clause
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the LibQxt project nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Files: debian/*
|
||||
Copyright: Dmitry Smirnov <onlyjob@debian.org>
|
||||
License: GPL-3+
|
||||
|
||||
License: GPL-3+
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3, or (at your option)
|
||||
any later version.
|
||||
.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
The complete text of the GNU General Public License can be found
|
||||
in "/usr/share/common-licenses/GPL-3".
|
||||
|
||||
License: LGPL-2.1
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License version 2.1 as published by the Free Software Foundation.
|
||||
.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
.
|
||||
The complete text of the GNU Lesser General Public License
|
||||
can be found in "/usr/share/common-licenses/LGPL-2.1".
|
||||
|
||||
License: LGPL-3
|
||||
This package is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License version 3
|
||||
as published by the Free Software Foundation.
|
||||
.
|
||||
This package is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
.
|
||||
The complete text fo the GNU Lesser General Public License version 3
|
||||
can be found in "/usr/share/common-licenses/LGPL-3"
|
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
export DEB_LDFLAGS_MAINT_APPEND += -Wl,--as-needed
|
||||
export QT_SELECT=5
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_configure:
|
||||
dh_auto_configure -- \
|
||||
-DCMAKE_VERBOSE_MAKEFILE=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DWITH_WEBKIT=1
|
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
export DEB_LDFLAGS_MAINT_APPEND += -Wl,--as-needed
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_configure:
|
||||
dh_auto_configure -- \
|
||||
-DCMAKE_VERBOSE_MAKEFILE=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DWITH_QT5=OFF \
|
||||
-DWITH_WEBKIT=1
|
@ -0,0 +1,55 @@
|
||||
%dir /usr/lib/debug
|
||||
%dir /usr/lib/debug/usr
|
||||
%dir /usr/lib/debug/usr/bin
|
||||
%dir /usr/lib/debug/usr/lib64
|
||||
%dir /usr/lib/debug/usr/lib64/copyq
|
||||
%dir /usr/lib/debug/usr/lib64/copyq/plugins
|
||||
%dir /usr/lib/debug/.build-id
|
||||
%dir /usr/lib/debug/.build-id/39
|
||||
%dir /usr/lib/debug/.build-id/40
|
||||
%dir /usr/lib/debug/.build-id/50
|
||||
%dir /usr/lib/debug/.build-id/e9
|
||||
%dir /usr/lib/debug/.build-id/ec
|
||||
%dir /usr/lib/debug/.build-id/25
|
||||
%dir /usr/lib/debug/.build-id/3c
|
||||
%dir /usr/lib/debug/.build-id/92
|
||||
%dir /usr/lib/debug/.build-id/48
|
||||
%dir /usr/lib/debug/.build-id/ca
|
||||
%dir /usr/lib/debug/.build-id/c9
|
||||
%dir /usr/lib/debug/.dwz
|
||||
/usr/lib/debug/usr/bin/copyq.debug
|
||||
/usr/lib/debug/usr/lib64/copyq/plugins/libitemdata.so.debug
|
||||
/usr/lib/debug/usr/lib64/copyq/plugins/libitemencrypted.so.debug
|
||||
/usr/lib/debug/usr/lib64/copyq/plugins/libitemfakevim.so.debug
|
||||
/usr/lib/debug/usr/lib64/copyq/plugins/libitemimage.so.debug
|
||||
/usr/lib/debug/usr/lib64/copyq/plugins/libitemnotes.so.debug
|
||||
/usr/lib/debug/usr/lib64/copyq/plugins/libitempinned.so.debug
|
||||
/usr/lib/debug/usr/lib64/copyq/plugins/libitemtags.so.debug
|
||||
/usr/lib/debug/usr/lib64/copyq/plugins/libitemtext.so.debug
|
||||
/usr/lib/debug/usr/lib64/copyq/plugins/libitemsync.so.debug
|
||||
/usr/lib/debug/usr/lib64/copyq/plugins/libitemweb.so.debug
|
||||
/usr/lib/debug/.build-id/39/6fb26cad0769eacd7af5a39c7fc2e9c51ea8d3
|
||||
/usr/lib/debug/.build-id/39/6fb26cad0769eacd7af5a39c7fc2e9c51ea8d3.debug
|
||||
/usr/lib/debug/.build-id/40/556e992b3e12ba6de9d1882d30b7e73824ba79
|
||||
/usr/lib/debug/.build-id/40/556e992b3e12ba6de9d1882d30b7e73824ba79.debug
|
||||
/usr/lib/debug/.build-id/50/851b8d260a05ae088cc78dcaa6a102464472b2
|
||||
/usr/lib/debug/.build-id/50/851b8d260a05ae088cc78dcaa6a102464472b2.debug
|
||||
/usr/lib/debug/.build-id/e9/4627e58f07e29f32c365d4d028acbdd0e89921
|
||||
/usr/lib/debug/.build-id/e9/4627e58f07e29f32c365d4d028acbdd0e89921.debug
|
||||
/usr/lib/debug/.build-id/ec/a114fc00cc483f6b814421f97ac456e0d12067
|
||||
/usr/lib/debug/.build-id/ec/a114fc00cc483f6b814421f97ac456e0d12067.debug
|
||||
/usr/lib/debug/.build-id/25/d434b121abb4897672d69bb712ce1be3d655f9
|
||||
/usr/lib/debug/.build-id/25/d434b121abb4897672d69bb712ce1be3d655f9.debug
|
||||
/usr/lib/debug/.build-id/3c/710c9050a1f474964b4a1386e82800e2c5e62d
|
||||
/usr/lib/debug/.build-id/3c/710c9050a1f474964b4a1386e82800e2c5e62d.debug
|
||||
/usr/lib/debug/.build-id/3c/0e7d1c8fdaa374d4ed98e50bfd758e379ae062
|
||||
/usr/lib/debug/.build-id/3c/0e7d1c8fdaa374d4ed98e50bfd758e379ae062.debug
|
||||
/usr/lib/debug/.build-id/92/dc76b6c1c5f939a944e21c7a83618f5f77cc3d
|
||||
/usr/lib/debug/.build-id/92/dc76b6c1c5f939a944e21c7a83618f5f77cc3d.debug
|
||||
/usr/lib/debug/.build-id/48/9fe1d84c8799004869412a6e133a5e1a105b97
|
||||
/usr/lib/debug/.build-id/48/9fe1d84c8799004869412a6e133a5e1a105b97.debug
|
||||
/usr/lib/debug/.build-id/ca/536be7713f249df8c76ab5378c30eed1944769
|
||||
/usr/lib/debug/.build-id/ca/536be7713f249df8c76ab5378c30eed1944769.debug
|
||||
/usr/lib/debug/.build-id/c9/c11dd588b71df9b878fb201fe399e64a211f6e.debug
|
||||
/usr/lib/debug/.dwz/copyq-3.0.2-1.fc27.x86_64
|
||||
/usr/src/debug/CopyQ-3.0.2
|
@ -0,0 +1,23 @@
|
||||
/usr/lib/debug/.build-id/39/6fb26cad0769eacd7af5a39c7fc2e9c51ea8d3 /usr/bin/copyq
|
||||
/usr/lib/debug/.build-id/39/6fb26cad0769eacd7af5a39c7fc2e9c51ea8d3.debug /usr/lib/debug/usr/bin/copyq.debug
|
||||
/usr/lib/debug/.build-id/40/556e992b3e12ba6de9d1882d30b7e73824ba79 /usr/lib64/copyq/plugins/libitemdata.so
|
||||
/usr/lib/debug/.build-id/40/556e992b3e12ba6de9d1882d30b7e73824ba79.debug /usr/lib/debug/usr/lib64/copyq/plugins/libitemdata.so.debug
|
||||
/usr/lib/debug/.build-id/50/851b8d260a05ae088cc78dcaa6a102464472b2 /usr/lib64/copyq/plugins/libitemencrypted.so
|
||||
/usr/lib/debug/.build-id/50/851b8d260a05ae088cc78dcaa6a102464472b2.debug /usr/lib/debug/usr/lib64/copyq/plugins/libitemencrypted.so.debug
|
||||
/usr/lib/debug/.build-id/e9/4627e58f07e29f32c365d4d028acbdd0e89921 /usr/lib64/copyq/plugins/libitemfakevim.so
|
||||
/usr/lib/debug/.build-id/e9/4627e58f07e29f32c365d4d028acbdd0e89921.debug /usr/lib/debug/usr/lib64/copyq/plugins/libitemfakevim.so.debug
|
||||
/usr/lib/debug/.build-id/ec/a114fc00cc483f6b814421f97ac456e0d12067 /usr/lib64/copyq/plugins/libitemimage.so
|
||||
/usr/lib/debug/.build-id/ec/a114fc00cc483f6b814421f97ac456e0d12067.debug /usr/lib/debug/usr/lib64/copyq/plugins/libitemimage.so.debug
|
||||
/usr/lib/debug/.build-id/25/d434b121abb4897672d69bb712ce1be3d655f9 /usr/lib64/copyq/plugins/libitemnotes.so
|
||||
/usr/lib/debug/.build-id/25/d434b121abb4897672d69bb712ce1be3d655f9.debug /usr/lib/debug/usr/lib64/copyq/plugins/libitemnotes.so.debug
|
||||
/usr/lib/debug/.build-id/3c/710c9050a1f474964b4a1386e82800e2c5e62d /usr/lib64/copyq/plugins/libitempinned.so
|
||||
/usr/lib/debug/.build-id/3c/710c9050a1f474964b4a1386e82800e2c5e62d.debug /usr/lib/debug/usr/lib64/copyq/plugins/libitempinned.so.debug
|
||||
/usr/lib/debug/.build-id/3c/0e7d1c8fdaa374d4ed98e50bfd758e379ae062 /usr/lib64/copyq/plugins/libitemtags.so
|
||||
/usr/lib/debug/.build-id/3c/0e7d1c8fdaa374d4ed98e50bfd758e379ae062.debug /usr/lib/debug/usr/lib64/copyq/plugins/libitemtags.so.debug
|
||||
/usr/lib/debug/.build-id/92/dc76b6c1c5f939a944e21c7a83618f5f77cc3d /usr/lib64/copyq/plugins/libitemtext.so
|
||||
/usr/lib/debug/.build-id/92/dc76b6c1c5f939a944e21c7a83618f5f77cc3d.debug /usr/lib/debug/usr/lib64/copyq/plugins/libitemtext.so.debug
|
||||
/usr/lib/debug/.build-id/48/9fe1d84c8799004869412a6e133a5e1a105b97 /usr/lib64/copyq/plugins/libitemsync.so
|
||||
/usr/lib/debug/.build-id/48/9fe1d84c8799004869412a6e133a5e1a105b97.debug /usr/lib/debug/usr/lib64/copyq/plugins/libitemsync.so.debug
|
||||
/usr/lib/debug/.build-id/ca/536be7713f249df8c76ab5378c30eed1944769 /usr/lib64/copyq/plugins/libitemweb.so
|
||||
/usr/lib/debug/.build-id/ca/536be7713f249df8c76ab5378c30eed1944769.debug /usr/lib/debug/usr/lib64/copyq/plugins/libitemweb.so.debug
|
||||
/usr/lib/debug/.build-id/c9/c11dd588b71df9b878fb201fe399e64a211f6e.debug /usr/lib/debug/.dwz/copyq-3.0.2-1.fc27.x86_64
|
Binary file not shown.
@ -0,0 +1,11 @@
|
||||
.//usr/bin/copyq
|
||||
.//usr/lib64/copyq/plugins/libitemdata.so
|
||||
.//usr/lib64/copyq/plugins/libitemencrypted.so
|
||||
.//usr/lib64/copyq/plugins/libitemfakevim.so
|
||||
.//usr/lib64/copyq/plugins/libitemimage.so
|
||||
.//usr/lib64/copyq/plugins/libitemnotes.so
|
||||
.//usr/lib64/copyq/plugins/libitempinned.so
|
||||
.//usr/lib64/copyq/plugins/libitemtags.so
|
||||
.//usr/lib64/copyq/plugins/libitemtext.so
|
||||
.//usr/lib64/copyq/plugins/libitemsync.so
|
||||
.//usr/lib64/copyq/plugins/libitemweb.so
|
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
@ -0,0 +1,66 @@
|
||||
/usr/share/icons/hicolor/scalable/apps/copyq-normal.svg
|
||||
/usr/share/icons/hicolor/scalable/apps/copyq-busy.svg
|
||||
/usr/share/applications/copyq.desktop
|
||||
/usr/share/appdata/copyq.appdata.xml
|
||||
/usr/share/icons/hicolor/16x16/apps/copyq.png
|
||||
/usr/share/icons/hicolor/16x16/apps/copyq-normal.png
|
||||
/usr/share/icons/hicolor/16x16/apps/copyq-busy.png
|
||||
/usr/share/icons/hicolor/22x22/apps/copyq.png
|
||||
/usr/share/icons/hicolor/22x22/apps/copyq-normal.png
|
||||
/usr/share/icons/hicolor/22x22/apps/copyq-busy.png
|
||||
/usr/share/icons/hicolor/24x24/apps/copyq.png
|
||||
/usr/share/icons/hicolor/24x24/apps/copyq-normal.png
|
||||
/usr/share/icons/hicolor/24x24/apps/copyq-busy.png
|
||||
/usr/share/icons/hicolor/32x32/apps/copyq.png
|
||||
/usr/share/icons/hicolor/32x32/apps/copyq-normal.png
|
||||
/usr/share/icons/hicolor/32x32/apps/copyq-busy.png
|
||||
/usr/share/icons/hicolor/48x48/apps/copyq.png
|
||||
/usr/share/icons/hicolor/48x48/apps/copyq-normal.png
|
||||
/usr/share/icons/hicolor/48x48/apps/copyq-busy.png
|
||||
/usr/share/icons/hicolor/64x64/apps/copyq.png
|
||||
/usr/share/icons/hicolor/64x64/apps/copyq-normal.png
|
||||
/usr/share/icons/hicolor/64x64/apps/copyq-busy.png
|
||||
/usr/share/icons/hicolor/128x128/apps/copyq.png
|
||||
/usr/share/icons/hicolor/128x128/apps/copyq-normal.png
|
||||
/usr/share/icons/hicolor/128x128/apps/copyq-busy.png
|
||||
/usr/share/copyq/themes/dark.ini
|
||||
/usr/share/copyq/themes/forest.ini
|
||||
/usr/share/copyq/themes/light.ini
|
||||
/usr/share/copyq/themes/paper.ini
|
||||
/usr/share/copyq/themes/simple.ini
|
||||
/usr/share/copyq/themes/solarized-dark.ini
|
||||
/usr/share/copyq/themes/solarized-light.ini
|
||||
/usr/share/copyq/themes/wine.ini
|
||||
/usr/bin/copyq
|
||||
/usr/share/copyq/locale/copyq_ar.qm
|
||||
/usr/share/copyq/locale/copyq_cs.qm
|
||||
/usr/share/copyq/locale/copyq_da.qm
|
||||
/usr/share/copyq/locale/copyq_de.qm
|
||||
/usr/share/copyq/locale/copyq_es.qm
|
||||
/usr/share/copyq/locale/copyq_fr.qm
|
||||
/usr/share/copyq/locale/copyq_hu.qm
|
||||
/usr/share/copyq/locale/copyq_it.qm
|
||||
/usr/share/copyq/locale/copyq_ja.qm
|
||||
/usr/share/copyq/locale/copyq_lt.qm
|
||||
/usr/share/copyq/locale/copyq_nb.qm
|
||||
/usr/share/copyq/locale/copyq_nl.qm
|
||||
/usr/share/copyq/locale/copyq_pl.qm
|
||||
/usr/share/copyq/locale/copyq_pt_BR.qm
|
||||
/usr/share/copyq/locale/copyq_pt_PT.qm
|
||||
/usr/share/copyq/locale/copyq_ru.qm
|
||||
/usr/share/copyq/locale/copyq_sk.qm
|
||||
/usr/share/copyq/locale/copyq_sv.qm
|
||||
/usr/share/copyq/locale/copyq_tr.qm
|
||||
/usr/share/copyq/locale/copyq_uk.qm
|
||||
/usr/share/copyq/locale/copyq_zh_CN.qm
|
||||
/usr/share/copyq/locale/copyq_zh_TW.qm
|
||||
/usr/lib64/copyq/plugins/libitemdata.so
|
||||
/usr/lib64/copyq/plugins/libitemencrypted.so
|
||||
/usr/lib64/copyq/plugins/libitemfakevim.so
|
||||
/usr/lib64/copyq/plugins/libitemimage.so
|
||||
/usr/lib64/copyq/plugins/libitemnotes.so
|
||||
/usr/lib64/copyq/plugins/libitempinned.so
|
||||
/usr/lib64/copyq/plugins/libitemtags.so
|
||||
/usr/lib64/copyq/plugins/libitemtext.so
|
||||
/usr/lib64/copyq/plugins/libitemsync.so
|
||||
/usr/lib64/copyq/plugins/libitemweb.so
|
@ -0,0 +1,65 @@
|
||||
macro (copyq_add_plugin)
|
||||
set(copyq_pkg ${ARGV0})
|
||||
|
||||
file(GLOB copyq_plugin_SOURCES
|
||||
${copyq_plugin_${copyq_pkg}_SOURCES}
|
||||
*.cpp
|
||||
../../src/item/itemwidget.cpp
|
||||
)
|
||||
file(GLOB copyq_plugin_FORMS
|
||||
${copyq_plugin_${copyq_pkg}_FORMS}
|
||||
*.ui
|
||||
)
|
||||
|
||||
if (WITH_TESTS)
|
||||
file(GLOB copyq_plugin_SOURCES ${copyq_plugin_SOURCES} tests/*.cpp)
|
||||
endif (WITH_TESTS)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR} ../../src)
|
||||
|
||||
if (WITH_QT5)
|
||||
include_directories(${Qt5Widgets_INCLUDES})
|
||||
add_definitions(${Qt5Widgets_DEFINITIONS})
|
||||
qt5_wrap_ui(copyq_plugin_FORMS_HEADERS ${copyq_plugin_FORMS})
|
||||
qt5_add_resources(copyq_plugin_RCC ${copyq_plugin_${copyq_pkg}_RESOURCES})
|
||||
else()
|
||||
include_directories(${QT_INCLUDES})
|
||||
add_definitions(${QT_DEFINITIONS})
|
||||
qt4_wrap_ui(copyq_plugin_FORMS_HEADERS ${copyq_plugin_FORMS})
|
||||
qt4_add_resources(copyq_plugin_RCC ${copyq_plugin_${copyq_pkg}_RESOURCES})
|
||||
endif()
|
||||
|
||||
add_library(${copyq_pkg} MODULE
|
||||
${copyq_plugin_SOURCES}
|
||||
${copyq_plugin_FORMS_HEADERS}
|
||||
${copyq_plugin_RCC}
|
||||
)
|
||||
|
||||
set_target_properties(${copyq_pkg} PROPERTIES
|
||||
COMPILE_DEFINITIONS "${copyq_plugin_${copyq_pkg}_DEFINITIONS}")
|
||||
|
||||
if (WITH_QT5)
|
||||
qt5_use_modules(${copyq_pkg} Widgets ${copyq_Qt5_Modules} ${copyq_plugin_${copyq_pkg}_Qt5_Modules})
|
||||
else()
|
||||
include(${QT_USE_FILE})
|
||||
endif()
|
||||
|
||||
target_link_libraries(${copyq_pkg} ${QT_LIBRARIES} ${copyq_plugin_${copyq_pkg}_LIBRARIES})
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
install(TARGETS ${copyq_pkg} DESTINATION ${PLUGIN_INSTALL_PREFIX})
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/plugins)
|
||||
|
||||
add_subdirectory("itemdata")
|
||||
add_subdirectory("itemencrypted")
|
||||
add_subdirectory("itemfakevim")
|
||||
add_subdirectory("itemimage")
|
||||
add_subdirectory("itemnotes")
|
||||
add_subdirectory("itempinned")
|
||||
add_subdirectory("itemtags")
|
||||
add_subdirectory("itemtext")
|
||||
add_subdirectory("itemsync")
|
||||
add_subdirectory("itemweb")
|
@ -0,0 +1,8 @@
|
||||
set(copyq_plugin_itemdata_SOURCES
|
||||
../../src/common/log.cpp
|
||||
../../src/common/mimetypes.cpp
|
||||
../../src/common/textdata.cpp
|
||||
)
|
||||
|
||||
copyq_add_plugin(itemdata)
|
||||
|
@ -0,0 +1,212 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemdata.h"
|
||||
#include "ui_itemdatasettings.h"
|
||||
|
||||
#include "common/contenttype.h"
|
||||
#include "common/mimetypes.h"
|
||||
#include "common/textdata.h"
|
||||
|
||||
#include <QContextMenuEvent>
|
||||
#include <QModelIndex>
|
||||
#include <QMouseEvent>
|
||||
#include <QTextCodec>
|
||||
#include <QtPlugin>
|
||||
|
||||
namespace {
|
||||
|
||||
// Limit number of characters for performance reasons.
|
||||
const int defaultMaxBytes = 256;
|
||||
|
||||
QString hexData(const QByteArray &data)
|
||||
{
|
||||
if ( data.isEmpty() )
|
||||
return QString();
|
||||
|
||||
QString result;
|
||||
QString chars;
|
||||
|
||||
int i = 0;
|
||||
forever {
|
||||
if (i > 0) {
|
||||
if ( (i % 2) == 0 )
|
||||
result.append( QString(" ") );
|
||||
if ( (i % 16) == 0 ) {
|
||||
result.append(" ");
|
||||
result.append(chars);
|
||||
result.append( QString("\n") );
|
||||
chars.clear();
|
||||
if (i >= data.size() )
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( (i % 16) == 0 ) {
|
||||
result.append( QString("%1: ").arg(QString::number(i, 16), 4, QChar('0')) );
|
||||
}
|
||||
if (i < data.size() ) {
|
||||
QChar c = data[i];
|
||||
result.append( QString("%1").arg(QString::number(c.unicode(), 16), 2, QChar('0')) );
|
||||
chars.append( c.isPrint() ? escapeHtml(QString(c)) : QString(".") );
|
||||
} else {
|
||||
result.append( QString(" ") );
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString stringFromBytes(const QByteArray &bytes, const QString &format)
|
||||
{
|
||||
QTextCodec *codec = QTextCodec::codecForName("utf-8");
|
||||
if (format == QLatin1String("text/html"))
|
||||
codec = QTextCodec::codecForHtml(bytes, codec);
|
||||
return codec->toUnicode(bytes);
|
||||
}
|
||||
|
||||
bool emptyIntersection(const QStringList &lhs, const QStringList &rhs)
|
||||
{
|
||||
for (const auto &l : lhs) {
|
||||
if ( rhs.contains(l) )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemData::ItemData(const QModelIndex &index, int maxBytes, QWidget *parent)
|
||||
: QLabel(parent)
|
||||
, ItemWidget(this)
|
||||
{
|
||||
setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
setContentsMargins(4, 4, 4, 4);
|
||||
setTextFormat(Qt::RichText);
|
||||
|
||||
QString text;
|
||||
|
||||
const QVariantMap data = index.data(contentType::data).toMap();
|
||||
for ( const auto &format : data.keys() ) {
|
||||
QByteArray bytes = data[format].toByteArray();
|
||||
const int size = bytes.size();
|
||||
bool trimmed = size > maxBytes;
|
||||
if (trimmed)
|
||||
bytes = bytes.left(maxBytes);
|
||||
|
||||
bool hasText = format.startsWith("text/") ||
|
||||
format.startsWith("application/x-copyq-owner-window-title");
|
||||
const QString content = hasText ? escapeHtml(stringFromBytes(bytes, format)) : hexData(bytes);
|
||||
text.append( QString("<p>") );
|
||||
text.append( QString("<b>%1</b> (%2 bytes)<pre>%3</pre>")
|
||||
.arg(format)
|
||||
.arg(size)
|
||||
.arg(content) );
|
||||
text.append( QString("</p>") );
|
||||
|
||||
if (trimmed)
|
||||
text.append( QString("<p>...</p>") );
|
||||
}
|
||||
|
||||
setText(text);
|
||||
}
|
||||
|
||||
void ItemData::highlight(const QRegExp &, const QFont &, const QPalette &)
|
||||
{
|
||||
}
|
||||
|
||||
void ItemData::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
QLabel::mousePressEvent(e);
|
||||
e->ignore();
|
||||
}
|
||||
|
||||
void ItemData::mouseDoubleClickEvent(QMouseEvent *e)
|
||||
{
|
||||
if ( e->modifiers().testFlag(Qt::ShiftModifier) )
|
||||
QLabel::mouseDoubleClickEvent(e);
|
||||
else
|
||||
e->ignore();
|
||||
}
|
||||
|
||||
void ItemData::contextMenuEvent(QContextMenuEvent *e)
|
||||
{
|
||||
e->ignore();
|
||||
}
|
||||
|
||||
ItemDataLoader::ItemDataLoader()
|
||||
{
|
||||
}
|
||||
|
||||
ItemDataLoader::~ItemDataLoader() = default;
|
||||
|
||||
ItemWidget *ItemDataLoader::create(const QModelIndex &index, QWidget *parent, bool preview) const
|
||||
{
|
||||
if ( index.data(contentType::isHidden).toBool() )
|
||||
return nullptr;
|
||||
|
||||
const QStringList formats = index.data(contentType::data).toMap().keys();
|
||||
if ( emptyIntersection(formats, formatsToSave()) )
|
||||
return nullptr;
|
||||
|
||||
const int bytes = preview ? 4096 : m_settings.value("max_bytes", defaultMaxBytes).toInt();
|
||||
return new ItemData(index, bytes, parent);
|
||||
}
|
||||
|
||||
QStringList ItemDataLoader::formatsToSave() const
|
||||
{
|
||||
return m_settings.contains("formats")
|
||||
? m_settings["formats"].toStringList()
|
||||
: QStringList() << mimeUriList << QString("text/xml");
|
||||
}
|
||||
|
||||
QVariantMap ItemDataLoader::applySettings()
|
||||
{
|
||||
Q_ASSERT(ui != nullptr);
|
||||
m_settings["formats"] = ui->plainTextEditFormats->toPlainText().split( QRegExp("[;,\\s]+") );
|
||||
m_settings["max_bytes"] = ui->spinBoxMaxChars->value();
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
QWidget *ItemDataLoader::createSettingsWidget(QWidget *parent)
|
||||
{
|
||||
ui.reset(new Ui::ItemDataSettings);
|
||||
QWidget *w = new QWidget(parent);
|
||||
ui->setupUi(w);
|
||||
|
||||
const QStringList formats = formatsToSave();
|
||||
ui->plainTextEditFormats->setPlainText( formats.join(QString("\n")) );
|
||||
ui->spinBoxMaxChars->setValue( m_settings.value("max_bytes", defaultMaxBytes).toInt() );
|
||||
|
||||
connect( ui->treeWidgetFormats, SIGNAL(itemActivated(QTreeWidgetItem*,int)),
|
||||
SLOT(on_treeWidgetFormats_itemActivated(QTreeWidgetItem*,int)) );
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
void ItemDataLoader::on_treeWidgetFormats_itemActivated(QTreeWidgetItem *item, int column)
|
||||
{
|
||||
const QString mime = item->toolTip(column);
|
||||
if ( !mime.isEmpty() )
|
||||
ui->plainTextEditFormats->appendPlainText(mime);
|
||||
}
|
||||
|
||||
Q_EXPORT_PLUGIN2(itemdata, ItemDataLoader)
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMDATA_H
|
||||
#define ITEMDATA_H
|
||||
|
||||
#include "item/itemwidget.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Ui {
|
||||
class ItemDataSettings;
|
||||
}
|
||||
|
||||
class QTreeWidgetItem;
|
||||
|
||||
class ItemData : public QLabel, public ItemWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ItemData(const QModelIndex &index, int maxBytes, QWidget *parent);
|
||||
|
||||
protected:
|
||||
void highlight(const QRegExp &re, const QFont &highlightFont,
|
||||
const QPalette &highlightPalette) override;
|
||||
|
||||
QWidget *createEditor(QWidget *) const override { return nullptr; }
|
||||
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
|
||||
void mouseDoubleClickEvent(QMouseEvent *e) override;
|
||||
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
};
|
||||
|
||||
class ItemDataLoader : public QObject, public ItemLoaderInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID COPYQ_PLUGIN_ITEM_LOADER_ID)
|
||||
Q_INTERFACES(ItemLoaderInterface)
|
||||
|
||||
public:
|
||||
ItemDataLoader();
|
||||
~ItemDataLoader();
|
||||
|
||||
ItemWidget *create(const QModelIndex &index, QWidget *parent, bool preview) const override;
|
||||
|
||||
int priority() const override { return -20; }
|
||||
|
||||
QString id() const override { return "itemdata"; }
|
||||
QString name() const override { return tr("Data"); }
|
||||
QString author() const override { return QString(); }
|
||||
QString description() const override { return tr("Various data to save."); }
|
||||
QVariant icon() const override { return QVariant(IconFileText); }
|
||||
|
||||
QStringList formatsToSave() const override;
|
||||
|
||||
QVariantMap applySettings() override;
|
||||
|
||||
void loadSettings(const QVariantMap &settings) override { m_settings = settings; }
|
||||
|
||||
QWidget *createSettingsWidget(QWidget *parent) override;
|
||||
|
||||
private slots:
|
||||
void on_treeWidgetFormats_itemActivated(QTreeWidgetItem *item, int column);
|
||||
|
||||
private:
|
||||
QVariantMap m_settings;
|
||||
std::unique_ptr<Ui::ItemDataSettings> ui;
|
||||
};
|
||||
|
||||
#endif // ITEMDATA_H
|
@ -0,0 +1,10 @@
|
||||
include(../plugins_common.pri)
|
||||
|
||||
HEADERS += itemdata.h
|
||||
SOURCES += itemdata.cpp \
|
||||
../../src/common/log.cpp \
|
||||
../../src/common/mimetypes.cpp \
|
||||
../../src/common/textdata.cpp
|
||||
FORMS += itemdatasettings.ui
|
||||
TARGET = $$qtLibraryTarget(itemdata)
|
||||
|
@ -0,0 +1,214 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ItemDataSettings</class>
|
||||
<widget class="QWidget" name="ItemDataSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>430</width>
|
||||
<height>321</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_26">
|
||||
<property name="text">
|
||||
<string>Select formats to save in history. You can add a format from examples below or type in other (one per line).</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Active &Formats:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>plainTextEditFormats</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>&Examples (double click to add to active formats):</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>treeWidgetFormats</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QTreeWidget" name="treeWidgetFormats">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Text</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Unformatted simple text</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string notr="true">text/plain</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Formatted text, web pages</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string notr="true">text/html
|
||||
text/richtext</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>XML</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string notr="true">text/xml</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>List of URI (e.g. copied files, URLs)</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string notr="true">text/uri-list</string>
|
||||
</property>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Images</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bitmap image</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string notr="true">text/bmp</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Vector graphics</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string notr="true">image/svg+xml
|
||||
image/x-inkscape-svg-compressed</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Web image formats</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string notr="true">image/png
|
||||
image/jpeg
|
||||
image/gif</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Other</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string notr="true">image/epx
|
||||
image/ico
|
||||
image/jp2
|
||||
image/jpx
|
||||
image/tiff
|
||||
image/pcx
|
||||
image/ppm
|
||||
image/rgb
|
||||
image/rgba
|
||||
image/tga
|
||||
image/x-targa
|
||||
image/x-tga
|
||||
image/xpm
|
||||
image/xbm</string>
|
||||
</property>
|
||||
</item>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPlainTextEdit" name="plainTextEditFormats">
|
||||
<property name="toolTip">
|
||||
<string>List of clipboard mime types that will be stored in history (in given display order)</string>
|
||||
</property>
|
||||
<property name="tabChangesFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="lineWrapMode">
|
||||
<enum>QPlainTextEdit::NoWrap</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Maximum number of characters per format to display:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>spinBoxMaxChars</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinBoxMaxChars">
|
||||
<property name="maximum">
|
||||
<number>4096</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,13 @@
|
||||
set(copyq_plugin_itemencrypted_SOURCES
|
||||
../../src/common/config.cpp
|
||||
../../src/common/log.cpp
|
||||
../../src/common/mimetypes.cpp
|
||||
../../src/common/shortcuts.cpp
|
||||
../../src/common/textdata.cpp
|
||||
../../src/gui/iconfont.cpp
|
||||
../../src/gui/iconwidget.cpp
|
||||
../../src/item/serialize.cpp
|
||||
)
|
||||
|
||||
copyq_add_plugin(itemencrypted)
|
||||
|
@ -0,0 +1,891 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemencrypted.h"
|
||||
#include "ui_itemencryptedsettings.h"
|
||||
|
||||
#include "common/command.h"
|
||||
#include "common/config.h"
|
||||
#include "common/contenttype.h"
|
||||
#include "common/log.h"
|
||||
#include "common/mimetypes.h"
|
||||
#include "common/shortcuts.h"
|
||||
#include "common/textdata.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/iconwidget.h"
|
||||
#include "item/serialize.h"
|
||||
|
||||
#ifdef HAS_TESTS
|
||||
# include "tests/itemencryptedtests.h"
|
||||
#endif
|
||||
|
||||
#include <QDir>
|
||||
#include <QIODevice>
|
||||
#include <QLabel>
|
||||
#include <QTextEdit>
|
||||
#include <QtPlugin>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace {
|
||||
|
||||
const char mimeEncryptedData[] = "application/x-copyq-encrypted";
|
||||
|
||||
const char dataFileHeader[] = "CopyQ_encrypted_tab";
|
||||
const char dataFileHeaderV2[] = "CopyQ_encrypted_tab v2";
|
||||
|
||||
const int maxItemCount = 10000;
|
||||
|
||||
struct KeyPairPaths {
|
||||
KeyPairPaths()
|
||||
{
|
||||
const QString path = getConfigurationFilePath(QString());
|
||||
sec = QDir::toNativeSeparators(path + ".sec");
|
||||
pub = QDir::toNativeSeparators(path + ".pub");
|
||||
}
|
||||
|
||||
QString sec;
|
||||
QString pub;
|
||||
};
|
||||
|
||||
QString gpgExecutable()
|
||||
{
|
||||
return "gpg2";
|
||||
}
|
||||
|
||||
QStringList getDefaultEncryptCommandArguments(const QString &publicKeyPath)
|
||||
{
|
||||
return QStringList() << "--trust-model" << "always" << "--recipient" << "copyq"
|
||||
<< "--charset" << "utf-8" << "--display-charset" << "utf-8" << "--no-tty"
|
||||
<< "--no-default-keyring" << "--keyring" << publicKeyPath;
|
||||
}
|
||||
|
||||
void startGpgProcess(QProcess *p, const QStringList &args)
|
||||
{
|
||||
KeyPairPaths keys;
|
||||
p->start(gpgExecutable(), getDefaultEncryptCommandArguments(keys.pub) + args);
|
||||
}
|
||||
|
||||
bool verifyProcess(QProcess *p)
|
||||
{
|
||||
const int exitCode = p->exitCode();
|
||||
if ( p->exitStatus() != QProcess::NormalExit ) {
|
||||
log( "ItemEncrypt ERROR: Failed to run GnuPG: " + p->errorString(), LogError );
|
||||
return false;
|
||||
}
|
||||
|
||||
if (exitCode != 0) {
|
||||
const QString errors = p->readAllStandardError();
|
||||
if ( !errors.isEmpty() )
|
||||
log( "ItemEncrypt ERROR: GnuPG stderr:\n" + errors, LogError );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool waitOrTerminate(QProcess *p)
|
||||
{
|
||||
if ( p->state() != QProcess::NotRunning && !p->waitForFinished() ) {
|
||||
p->terminate();
|
||||
if ( !p->waitForFinished(5000) )
|
||||
p->kill();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString importGpgKey()
|
||||
{
|
||||
KeyPairPaths keys;
|
||||
|
||||
QProcess p;
|
||||
p.start(gpgExecutable(), getDefaultEncryptCommandArguments(keys.pub) << "--import" << keys.sec);
|
||||
if ( !waitOrTerminate(&p) )
|
||||
return "Failed to import private key (process timed out).";
|
||||
|
||||
if ( !verifyProcess(&p) )
|
||||
return "Failed to import private key (see log).";
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString exportGpgKey()
|
||||
{
|
||||
KeyPairPaths keys;
|
||||
|
||||
// Private key already created or exported.
|
||||
if ( QFile::exists(keys.sec) )
|
||||
return QString();
|
||||
|
||||
QProcess p;
|
||||
p.start(gpgExecutable(), getDefaultEncryptCommandArguments(keys.pub) << "--export-secret-key" << "copyq");
|
||||
if ( !waitOrTerminate(&p) )
|
||||
return "Failed to export private key (process timed out).";
|
||||
|
||||
if ( !verifyProcess(&p) )
|
||||
return "Failed to export private key (see log).";
|
||||
|
||||
QFile secKey(keys.sec);
|
||||
if ( !secKey.open(QIODevice::WriteOnly) )
|
||||
return "Failed to create private key.";
|
||||
|
||||
if ( !secKey.setPermissions(QFile::ReadOwner | QFile::WriteOwner) )
|
||||
return "Failed to set permissions for private key.";
|
||||
|
||||
const QByteArray secKeyData = p.readAllStandardOutput();
|
||||
secKey.write(secKeyData);
|
||||
secKey.close();
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QByteArray readGpgOutput(const QStringList &args, const QByteArray &input = QByteArray())
|
||||
{
|
||||
QProcess p;
|
||||
startGpgProcess( &p, args );
|
||||
p.write(input);
|
||||
p.closeWriteChannel();
|
||||
p.waitForFinished();
|
||||
verifyProcess(&p);
|
||||
return p.readAllStandardOutput();
|
||||
}
|
||||
|
||||
bool keysExist()
|
||||
{
|
||||
return !readGpgOutput( QStringList("--list-keys") ).isEmpty();
|
||||
}
|
||||
|
||||
bool decryptMimeData(QVariantMap *detinationData, const QModelIndex &index)
|
||||
{
|
||||
const QVariantMap data = index.data(contentType::data).toMap();
|
||||
if ( !data.contains(mimeEncryptedData) )
|
||||
return false;
|
||||
|
||||
const QByteArray encryptedBytes = data.value(mimeEncryptedData).toByteArray();
|
||||
const QByteArray bytes = readGpgOutput( QStringList() << "--decrypt", encryptedBytes );
|
||||
|
||||
return deserializeData(detinationData, bytes);
|
||||
}
|
||||
|
||||
void encryptMimeData(const QVariantMap &data, const QModelIndex &index, QAbstractItemModel *model)
|
||||
{
|
||||
const QByteArray bytes = serializeData(data);
|
||||
const QByteArray encryptedBytes = readGpgOutput( QStringList("--encrypt"), bytes );
|
||||
QVariantMap dataMap;
|
||||
dataMap.insert(mimeEncryptedData, encryptedBytes);
|
||||
model->setData(index, dataMap, contentType::data);
|
||||
}
|
||||
|
||||
void startGenerateKeysProcess(QProcess *process, bool useTransientPasswordlessKey = false)
|
||||
{
|
||||
const KeyPairPaths keys;
|
||||
|
||||
auto args = QStringList() << "--batch" << "--gen-key";
|
||||
|
||||
QByteArray transientOptions;
|
||||
if (useTransientPasswordlessKey) {
|
||||
args << "--debug-quick-random";
|
||||
transientOptions =
|
||||
"\n%no-protection"
|
||||
"\n%transient-key";
|
||||
}
|
||||
|
||||
startGpgProcess(process, args);
|
||||
process->write( "\nKey-Type: RSA"
|
||||
"\nKey-Usage: encrypt"
|
||||
"\nKey-Length: 2048"
|
||||
"\nName-Real: copyq"
|
||||
+ transientOptions +
|
||||
"\n%secring " + keys.sec.toUtf8() +
|
||||
"\n%pubring " + keys.pub.toUtf8() +
|
||||
"\n%commit"
|
||||
"\n" );
|
||||
process->closeWriteChannel();
|
||||
}
|
||||
|
||||
QString exportImportGpgKeys()
|
||||
{
|
||||
const auto error = exportGpgKey();
|
||||
if ( !error.isEmpty() )
|
||||
return error;
|
||||
|
||||
return importGpgKey();
|
||||
}
|
||||
|
||||
bool isGpgInstalled()
|
||||
{
|
||||
QProcess p;
|
||||
startGpgProcess(&p, QStringList("--version"));
|
||||
p.closeWriteChannel();
|
||||
p.waitForFinished();
|
||||
|
||||
if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0)
|
||||
return false;
|
||||
|
||||
const auto versionOutput = p.readAllStandardOutput();
|
||||
return versionOutput.contains(" 2.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemEncrypted::ItemEncrypted(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, ItemWidget(this)
|
||||
{
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// Show small icon.
|
||||
QWidget *iconWidget = new IconWidget(IconLock, this);
|
||||
layout->addWidget(iconWidget);
|
||||
}
|
||||
|
||||
void ItemEncrypted::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
// Decrypt before editing.
|
||||
QTextEdit *textEdit = qobject_cast<QTextEdit *>(editor);
|
||||
if (textEdit != nullptr) {
|
||||
QVariantMap data;
|
||||
if ( decryptMimeData(&data, index) ) {
|
||||
textEdit->setPlainText( getTextData(data, mimeText) );
|
||||
textEdit->selectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ItemEncrypted::setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
// Encrypt after editing.
|
||||
QTextEdit *textEdit = qobject_cast<QTextEdit*>(editor);
|
||||
if (textEdit != nullptr)
|
||||
encryptMimeData( createDataMap(mimeText, textEdit->toPlainText()), index, model );
|
||||
}
|
||||
|
||||
bool ItemEncryptedSaver::saveItems(const QString &, const QAbstractItemModel &model, QIODevice *file)
|
||||
{
|
||||
const auto length = model.rowCount();
|
||||
if (length == 0)
|
||||
return false; // No need to encode empty tab.
|
||||
|
||||
QByteArray bytes;
|
||||
|
||||
{
|
||||
QDataStream stream(&bytes, QIODevice::WriteOnly);
|
||||
stream.setVersion(QDataStream::Qt_4_7);
|
||||
|
||||
stream << static_cast<quint64>(length);
|
||||
|
||||
for (int i = 0; i < length && stream.status() == QDataStream::Ok; ++i) {
|
||||
QModelIndex index = model.index(i, 0);
|
||||
const QVariantMap dataMap = index.data(contentType::data).toMap();
|
||||
stream << dataMap;
|
||||
}
|
||||
}
|
||||
|
||||
bytes = readGpgOutput(QStringList("--encrypt"), bytes);
|
||||
if ( bytes.isEmpty() ) {
|
||||
emitEncryptFailed();
|
||||
COPYQ_LOG("ItemEncrypt ERROR: Failed to read encrypted data");
|
||||
return false;
|
||||
}
|
||||
|
||||
QDataStream stream(file);
|
||||
stream << QString(dataFileHeaderV2);
|
||||
stream.writeRawData( bytes.data(), bytes.size() );
|
||||
|
||||
if ( stream.status() != QDataStream::Ok ) {
|
||||
emitEncryptFailed();
|
||||
COPYQ_LOG("ItemEncrypt ERROR: Failed to write encrypted data");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ItemEncryptedSaver::emitEncryptFailed()
|
||||
{
|
||||
emit error( ItemEncryptedLoader::tr("Encryption failed!") );
|
||||
}
|
||||
|
||||
bool ItemEncryptedScriptable::isEncrypted()
|
||||
{
|
||||
const auto args = currentArguments();
|
||||
for (const auto &arg : args) {
|
||||
bool ok;
|
||||
const int row = arg.toInt(&ok);
|
||||
if (ok) {
|
||||
const auto result = call("read", QVariantList() << "?" << row);
|
||||
if ( result.toByteArray().contains(mimeEncryptedData) )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray ItemEncryptedScriptable::encrypt()
|
||||
{
|
||||
const auto args = currentArguments();
|
||||
const auto bytes = args.first().toByteArray();
|
||||
return encrypt(bytes);
|
||||
}
|
||||
|
||||
QByteArray ItemEncryptedScriptable::decrypt()
|
||||
{
|
||||
const auto args = currentArguments();
|
||||
const auto bytes = args.first().toByteArray();
|
||||
return decrypt(bytes);
|
||||
}
|
||||
|
||||
void ItemEncryptedScriptable::encryptItem()
|
||||
{
|
||||
QVariantMap dataMap;
|
||||
const auto formats = call("dataFormats").toList();
|
||||
for (const auto &formatValue : formats) {
|
||||
const auto format = formatValue.toString();
|
||||
if ( !format.startsWith(COPYQ_MIME_PREFIX) ) {
|
||||
const auto data = call("data", QVariantList() << format).toByteArray();
|
||||
dataMap.insert(format, data);
|
||||
}
|
||||
}
|
||||
|
||||
const auto bytes = call("pack", QVariantList() << dataMap).toByteArray();
|
||||
const auto encryptedBytes = encrypt(bytes);
|
||||
if (encryptedBytes.isEmpty())
|
||||
return;
|
||||
|
||||
call("setData", QVariantList() << mimeEncryptedData << encryptedBytes);
|
||||
|
||||
for ( const auto &format : dataMap.keys() )
|
||||
call("removeData", QVariantList() << format);
|
||||
}
|
||||
|
||||
void ItemEncryptedScriptable::decryptItem()
|
||||
{
|
||||
const auto encryptedBytes = call("data", QVariantList() << mimeEncryptedData).toByteArray();
|
||||
const auto itemData = decrypt(encryptedBytes);
|
||||
if (itemData.isEmpty())
|
||||
return;
|
||||
|
||||
const auto dataMap = call("unpack", QVariantList() << itemData).toMap();
|
||||
for ( const auto &format : dataMap.keys() )
|
||||
call("setData", QVariantList() << format << dataMap[format]);
|
||||
}
|
||||
|
||||
void ItemEncryptedScriptable::encryptItems()
|
||||
{
|
||||
const auto dataValueList = call("selectedItemsData").toList();
|
||||
|
||||
QVariantList dataList;
|
||||
for (const auto &itemDataValue : dataValueList) {
|
||||
auto itemData = itemDataValue.toMap();
|
||||
|
||||
QVariantMap itemDataToEncrypt;
|
||||
for ( const auto &format : itemData.keys() ) {
|
||||
if ( !format.startsWith(COPYQ_MIME_PREFIX) ) {
|
||||
itemDataToEncrypt.insert(format, itemData[format]);
|
||||
itemData.remove(format);
|
||||
}
|
||||
}
|
||||
|
||||
const auto bytes = call("pack", QVariantList() << itemDataToEncrypt).toByteArray();
|
||||
const auto encryptedBytes = encrypt(bytes);
|
||||
if (encryptedBytes.isEmpty())
|
||||
return;
|
||||
itemData.insert(mimeEncryptedData, encryptedBytes);
|
||||
|
||||
dataList.append(itemData);
|
||||
}
|
||||
|
||||
call( "setSelectedItemsData", QVariantList() << QVariant(dataList) );
|
||||
}
|
||||
|
||||
void ItemEncryptedScriptable::decryptItems()
|
||||
{
|
||||
const auto dataValueList = call("selectedItemsData").toList();
|
||||
|
||||
QVariantList dataList;
|
||||
for (const auto &itemDataValue : dataValueList) {
|
||||
auto itemData = itemDataValue.toMap();
|
||||
|
||||
const auto encryptedBytes = itemData.value(mimeEncryptedData).toByteArray();
|
||||
if ( !encryptedBytes.isEmpty() ) {
|
||||
itemData.remove(mimeEncryptedData);
|
||||
|
||||
const auto decryptedBytes = decrypt(encryptedBytes);
|
||||
if (decryptedBytes.isEmpty())
|
||||
return;
|
||||
|
||||
const auto decryptedItemData = call("unpack", QVariantList() << decryptedBytes).toMap();
|
||||
for ( const auto &format : decryptedItemData.keys() )
|
||||
itemData.insert(format, decryptedItemData[format]);
|
||||
}
|
||||
|
||||
dataList.append(itemData);
|
||||
}
|
||||
|
||||
call( "setSelectedItemsData", QVariantList() << QVariant(dataList) );
|
||||
}
|
||||
|
||||
void ItemEncryptedScriptable::copyEncryptedItems()
|
||||
{
|
||||
const auto dataValueList = call("selectedItemsData").toList();
|
||||
QString text;
|
||||
for (const auto &dataValue : dataValueList) {
|
||||
if ( !text.isEmpty() )
|
||||
text.append('\n');
|
||||
|
||||
const auto data = dataValue.toMap();
|
||||
const auto itemTextValue = data.value(mimeText);
|
||||
if ( itemTextValue.isValid() ) {
|
||||
text.append( getTextData(itemTextValue.toByteArray()) );
|
||||
} else {
|
||||
const auto encryptedBytes = data.value(mimeEncryptedData).toByteArray();
|
||||
if ( !encryptedBytes.isEmpty() ) {
|
||||
const auto itemData = decrypt(encryptedBytes);
|
||||
if (itemData.isEmpty())
|
||||
return;
|
||||
const auto dataMap = call("unpack", QVariantList() << itemData).toMap();
|
||||
text.append( getTextData(dataMap) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
call("copy", QVariantList() << text);
|
||||
}
|
||||
|
||||
QString ItemEncryptedScriptable::generateTestKeys()
|
||||
{
|
||||
const KeyPairPaths keys;
|
||||
for ( const auto &keyFileName : {keys.sec, keys.pub} ) {
|
||||
if ( QFile::exists(keyFileName) && !QFile::remove(keyFileName) )
|
||||
return QString("Failed to remove \"%1\"").arg(keys.sec);
|
||||
}
|
||||
|
||||
QProcess process;
|
||||
startGenerateKeysProcess(&process, true);
|
||||
|
||||
if ( !waitOrTerminate(&process) || !verifyProcess(&process) ) {
|
||||
return QString("ItemEncrypt ERROR: %1; stderr: %2")
|
||||
.arg( process.errorString() )
|
||||
.arg( QString::fromUtf8(process.readAllStandardError()) );
|
||||
}
|
||||
|
||||
const auto error = exportImportGpgKeys();
|
||||
if ( !error.isEmpty() )
|
||||
return error;
|
||||
|
||||
for ( const auto &keyFileName : {keys.sec, keys.pub} ) {
|
||||
if ( !QFile::exists(keyFileName) )
|
||||
return QString("Failed to create \"%1\"").arg(keys.sec);
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool ItemEncryptedScriptable::isGpgInstalled()
|
||||
{
|
||||
return ::isGpgInstalled();
|
||||
}
|
||||
|
||||
QByteArray ItemEncryptedScriptable::encrypt(const QByteArray &bytes)
|
||||
{
|
||||
const auto encryptedBytes = readGpgOutput(QStringList("--encrypt"), bytes);
|
||||
if ( encryptedBytes.isEmpty() )
|
||||
eval("throw 'Failed to execute GPG!'");
|
||||
return encryptedBytes;
|
||||
}
|
||||
|
||||
QByteArray ItemEncryptedScriptable::decrypt(const QByteArray &bytes)
|
||||
{
|
||||
const auto decryptedBytes = readGpgOutput(QStringList("--decrypt"), bytes);
|
||||
if ( decryptedBytes.isEmpty() )
|
||||
eval("throw 'Failed to execute GPG!'");
|
||||
return decryptedBytes;
|
||||
}
|
||||
|
||||
ItemEncryptedLoader::ItemEncryptedLoader()
|
||||
: ui()
|
||||
, m_settings()
|
||||
, m_gpgProcessStatus(GpgNotRunning)
|
||||
, m_gpgProcess(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ItemEncryptedLoader::~ItemEncryptedLoader()
|
||||
{
|
||||
terminateGpgProcess();
|
||||
}
|
||||
|
||||
ItemWidget *ItemEncryptedLoader::create(const QModelIndex &index, QWidget *parent, bool) const
|
||||
{
|
||||
if ( index.data(contentType::isHidden).toBool() )
|
||||
return nullptr;
|
||||
|
||||
const QVariantMap dataMap = index.data(contentType::data).toMap();
|
||||
return dataMap.contains(mimeEncryptedData) ? new ItemEncrypted(parent) : nullptr;
|
||||
}
|
||||
|
||||
QStringList ItemEncryptedLoader::formatsToSave() const
|
||||
{
|
||||
return QStringList(mimeEncryptedData);
|
||||
}
|
||||
|
||||
QVariantMap ItemEncryptedLoader::applySettings()
|
||||
{
|
||||
Q_ASSERT(ui != nullptr);
|
||||
m_settings.insert( "encrypt_tabs", ui->plainTextEditEncryptTabs->toPlainText().split('\n') );
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
QWidget *ItemEncryptedLoader::createSettingsWidget(QWidget *parent)
|
||||
{
|
||||
ui.reset(new Ui::ItemEncryptedSettings);
|
||||
QWidget *w = new QWidget(parent);
|
||||
ui->setupUi(w);
|
||||
|
||||
connect( ui->pushButtonAddCommands, SIGNAL(clicked()),
|
||||
this, SLOT(addCommands()) );
|
||||
|
||||
ui->plainTextEditEncryptTabs->setPlainText(
|
||||
m_settings.value("encrypt_tabs").toStringList().join("\n") );
|
||||
|
||||
// Check if gpg application is available.
|
||||
if ( !isGpgInstalled() ) {
|
||||
m_gpgProcessStatus = GpgNotInstalled;
|
||||
} else {
|
||||
KeyPairPaths keys;
|
||||
ui->labelShareInfo->setTextFormat(Qt::RichText);
|
||||
ui->labelShareInfo->setText( tr("To share encrypted items on other computer or"
|
||||
" session, you'll need public and secret key files:"
|
||||
"<ul>"
|
||||
"<li>%1</li>"
|
||||
"<li>%2<br />(Keep this secret key in a safe place.)</li>"
|
||||
"</ul>"
|
||||
)
|
||||
.arg( quoteString(keys.pub) )
|
||||
.arg( quoteString(keys.sec) )
|
||||
);
|
||||
}
|
||||
|
||||
updateUi();
|
||||
|
||||
connect( ui->pushButtonPassword, SIGNAL(clicked()),
|
||||
this, SLOT(setPassword()) );
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
bool ItemEncryptedLoader::canLoadItems(QIODevice *file) const
|
||||
{
|
||||
QDataStream stream(file);
|
||||
|
||||
QString header;
|
||||
stream >> header;
|
||||
|
||||
return stream.status() == QDataStream::Ok
|
||||
&& (header == dataFileHeader || header == dataFileHeaderV2);
|
||||
}
|
||||
|
||||
bool ItemEncryptedLoader::canSaveItems(const QString &tabName) const
|
||||
{
|
||||
for ( const auto &encryptTabName : m_settings.value("encrypt_tabs").toStringList() ) {
|
||||
if ( encryptTabName.isEmpty() )
|
||||
continue;
|
||||
|
||||
QString tabName1 = tabName;
|
||||
|
||||
// Ignore ampersands (usually just for underlining mnemonics) if none is specified.
|
||||
if ( !hasKeyHint(encryptTabName) )
|
||||
removeKeyHint(&tabName1);
|
||||
|
||||
// Ignore path in tab tree if none path separator is specified.
|
||||
if ( !encryptTabName.contains('/') ) {
|
||||
const int i = tabName1.lastIndexOf('/');
|
||||
tabName1.remove(0, i + 1);
|
||||
}
|
||||
|
||||
if ( tabName1 == encryptTabName )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemSaverPtr ItemEncryptedLoader::loadItems(const QString &, QAbstractItemModel *model, QIODevice *file, int maxItems)
|
||||
{
|
||||
// This is needed to skip header.
|
||||
if ( !canLoadItems(file) )
|
||||
return nullptr;
|
||||
|
||||
if (m_gpgProcessStatus == GpgNotInstalled) {
|
||||
emit error( tr("GnuPG must be installed to view encrypted tabs.") );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
importGpgKey();
|
||||
|
||||
QProcess p;
|
||||
startGpgProcess( &p, QStringList("--decrypt") );
|
||||
|
||||
char encryptedBytes[4096];
|
||||
|
||||
QDataStream stream(file);
|
||||
while ( !stream.atEnd() ) {
|
||||
const int bytesRead = stream.readRawData(encryptedBytes, 4096);
|
||||
if (bytesRead == -1) {
|
||||
emitDecryptFailed();
|
||||
COPYQ_LOG("ItemEncrypted ERROR: Failed to read encrypted data");
|
||||
return nullptr;
|
||||
}
|
||||
p.write(encryptedBytes, bytesRead);
|
||||
}
|
||||
|
||||
p.closeWriteChannel();
|
||||
if ( !waitOrTerminate(&p) || !verifyProcess(&p) ) {
|
||||
emitDecryptFailed();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QByteArray bytes = p.readAllStandardOutput();
|
||||
if ( bytes.isEmpty() ) {
|
||||
emitDecryptFailed();
|
||||
COPYQ_LOG("ItemEncrypt ERROR: Failed to read encrypted data.");
|
||||
verifyProcess(&p);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QDataStream stream2(bytes);
|
||||
|
||||
quint64 length;
|
||||
stream2 >> length;
|
||||
if ( length <= 0 || stream2.status() != QDataStream::Ok ) {
|
||||
emitDecryptFailed();
|
||||
COPYQ_LOG("ItemEncrypt ERROR: Failed to parse item count!");
|
||||
return nullptr;
|
||||
}
|
||||
length = qMin(length, static_cast<quint64>(maxItems)) - static_cast<quint64>(model->rowCount());
|
||||
|
||||
const auto count = length < maxItemCount ? static_cast<int>(length) : maxItemCount;
|
||||
for ( int i = 0; i < count && stream2.status() == QDataStream::Ok; ++i ) {
|
||||
if ( !model->insertRow(i) ) {
|
||||
emitDecryptFailed();
|
||||
COPYQ_LOG("ItemEncrypt ERROR: Failed to insert item!");
|
||||
return nullptr;
|
||||
}
|
||||
QVariantMap dataMap;
|
||||
stream2 >> dataMap;
|
||||
model->setData( model->index(i, 0), dataMap, contentType::data );
|
||||
}
|
||||
|
||||
if ( stream2.status() != QDataStream::Ok ) {
|
||||
emitDecryptFailed();
|
||||
COPYQ_LOG("ItemEncrypt ERROR: Failed to decrypt item!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return createSaver();
|
||||
}
|
||||
|
||||
ItemSaverPtr ItemEncryptedLoader::initializeTab(const QString &, QAbstractItemModel *, int)
|
||||
{
|
||||
if (m_gpgProcessStatus == GpgNotInstalled)
|
||||
return nullptr;
|
||||
|
||||
return createSaver();
|
||||
}
|
||||
|
||||
QObject *ItemEncryptedLoader::tests(const TestInterfacePtr &test) const
|
||||
{
|
||||
#ifdef HAS_TESTS
|
||||
QObject *tests = new ItemEncryptedTests(test);
|
||||
return tests;
|
||||
#else
|
||||
Q_UNUSED(test);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
ItemScriptable *ItemEncryptedLoader::scriptableObject(QObject *parent)
|
||||
{
|
||||
return new ItemEncryptedScriptable(parent);
|
||||
}
|
||||
|
||||
QList<Command> ItemEncryptedLoader::commands() const
|
||||
{
|
||||
QList<Command> commands;
|
||||
|
||||
Command c;
|
||||
c.name = tr("Encrypt (needs GnuPG)");
|
||||
c.icon = QString(QChar(IconLock));
|
||||
c.input = "!OUTPUT";
|
||||
c.output = mimeEncryptedData;
|
||||
c.inMenu = true;
|
||||
c.cmd = "copyq: plugins.itemencrypted.encryptItems()";
|
||||
c.shortcuts.append( toPortableShortcutText(tr("Ctrl+L")) );
|
||||
commands.append(c);
|
||||
|
||||
c = Command();
|
||||
c.name = tr("Decrypt");
|
||||
c.icon = QString(QChar(IconUnlock));
|
||||
c.input = mimeEncryptedData;
|
||||
c.output = mimeItems;
|
||||
c.inMenu = true;
|
||||
c.cmd = "copyq: plugins.itemencrypted.decryptItems()";
|
||||
c.shortcuts.append( toPortableShortcutText(tr("Ctrl+L")) );
|
||||
commands.append(c);
|
||||
|
||||
c = Command();
|
||||
c.name = tr("Decrypt and Copy");
|
||||
c.icon = QString(QChar(IconUnlockAlt));
|
||||
c.input = mimeEncryptedData;
|
||||
c.inMenu = true;
|
||||
c.cmd = "copyq: plugins.itemencrypted.copyEncryptedItems()";
|
||||
c.shortcuts.append( toPortableShortcutText(tr("Ctrl+Shift+L")) );
|
||||
commands.append(c);
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
void ItemEncryptedLoader::setPassword()
|
||||
{
|
||||
if (m_gpgProcessStatus == GpgGeneratingKeys)
|
||||
return;
|
||||
|
||||
if (m_gpgProcess != nullptr) {
|
||||
terminateGpgProcess();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !keysExist() ) {
|
||||
m_gpgProcessStatus = GpgGeneratingKeys;
|
||||
m_gpgProcess = new QProcess(this);
|
||||
startGenerateKeysProcess(m_gpgProcess);
|
||||
} else {
|
||||
// Change password.
|
||||
m_gpgProcessStatus = GpgChangingPassword;
|
||||
m_gpgProcess = new QProcess(this);
|
||||
startGpgProcess( m_gpgProcess, QStringList() << "--edit-key" << "copyq" << "passwd" << "save");
|
||||
}
|
||||
|
||||
m_gpgProcess->waitForStarted();
|
||||
if ( m_gpgProcess->state() == QProcess::NotRunning ) {
|
||||
onGpgProcessFinished( m_gpgProcess->exitCode(), m_gpgProcess->exitStatus() );
|
||||
} else {
|
||||
connect( m_gpgProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
|
||||
this, SLOT(onGpgProcessFinished(int,QProcess::ExitStatus)) );
|
||||
updateUi();
|
||||
}
|
||||
}
|
||||
|
||||
void ItemEncryptedLoader::terminateGpgProcess()
|
||||
{
|
||||
if (m_gpgProcess == nullptr)
|
||||
return;
|
||||
QProcess *p = m_gpgProcess;
|
||||
m_gpgProcess = nullptr;
|
||||
p->terminate();
|
||||
p->waitForFinished();
|
||||
p->deleteLater();
|
||||
m_gpgProcessStatus = GpgNotRunning;
|
||||
updateUi();
|
||||
}
|
||||
|
||||
void ItemEncryptedLoader::onGpgProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
{
|
||||
QString error;
|
||||
|
||||
if (m_gpgProcess != nullptr) {
|
||||
if (ui != nullptr) {
|
||||
if (exitStatus != QProcess::NormalExit)
|
||||
error = m_gpgProcess->errorString();
|
||||
else if (exitCode != 0)
|
||||
error = getTextData(m_gpgProcess->readAllStandardError());
|
||||
else if ( m_gpgProcess->error() != QProcess::UnknownError )
|
||||
error = m_gpgProcess->errorString();
|
||||
else if ( !keysExist() )
|
||||
error = tr("Failed to generate keys.");
|
||||
}
|
||||
|
||||
m_gpgProcess->deleteLater();
|
||||
m_gpgProcess = nullptr;
|
||||
}
|
||||
|
||||
// Export and import private key to a file in configuration.
|
||||
if ( m_gpgProcessStatus == GpgGeneratingKeys && error.isEmpty() )
|
||||
error = exportImportGpgKeys();
|
||||
|
||||
if (!error.isEmpty())
|
||||
error = tr("Error: %1").arg(error);
|
||||
|
||||
m_gpgProcessStatus = GpgNotRunning;
|
||||
|
||||
updateUi();
|
||||
ui->labelInfo->setText( error.isEmpty() ? tr("Done") : error );
|
||||
}
|
||||
|
||||
void ItemEncryptedLoader::addCommands()
|
||||
{
|
||||
emit addCommands(commands());
|
||||
}
|
||||
|
||||
void ItemEncryptedLoader::updateUi()
|
||||
{
|
||||
if (ui == nullptr)
|
||||
return;
|
||||
|
||||
if (m_gpgProcessStatus == GpgNotInstalled) {
|
||||
ui->labelInfo->setText("To use item encryption, install"
|
||||
" <a href=\"http://www.gnupg.org/\">GnuPG</a>"
|
||||
" application and restart CopyQ.");
|
||||
ui->pushButtonPassword->hide();
|
||||
ui->pushButtonAddCommands->hide();
|
||||
ui->groupBoxEncryptTabs->hide();
|
||||
ui->groupBoxShareInfo->hide();
|
||||
} else if (m_gpgProcessStatus == GpgGeneratingKeys) {
|
||||
ui->labelInfo->setText( tr("Creating new keys (this may take a few minutes)...") );
|
||||
ui->pushButtonPassword->setText( tr("Cancel") );
|
||||
} else if (m_gpgProcessStatus == GpgChangingPassword) {
|
||||
ui->labelInfo->setText( tr("Setting new password...") );
|
||||
ui->pushButtonPassword->setText( tr("Cancel") );
|
||||
} else if ( !keysExist() ) {
|
||||
ui->labelInfo->setText( tr("Encryption keys <strong>must be generated</strong>"
|
||||
" before item encryption can be used.") );
|
||||
ui->pushButtonPassword->setText( tr("Generate New Keys...") );
|
||||
} else {
|
||||
ui->pushButtonPassword->setText( tr("Change Password...") );
|
||||
}
|
||||
}
|
||||
|
||||
void ItemEncryptedLoader::emitDecryptFailed()
|
||||
{
|
||||
emit error( tr("Decryption failed!") );
|
||||
}
|
||||
|
||||
ItemSaverPtr ItemEncryptedLoader::createSaver()
|
||||
{
|
||||
auto saver = std::make_shared<ItemEncryptedSaver>();
|
||||
connect( saver.get(), SIGNAL(error(QString)),
|
||||
this, SIGNAL(error(QString)) );
|
||||
return saver;
|
||||
}
|
||||
|
||||
Q_EXPORT_PLUGIN2(itemencrypted, ItemEncryptedLoader)
|
@ -0,0 +1,165 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMENCRYPTED_H
|
||||
#define ITEMENCRYPTED_H
|
||||
|
||||
#include "item/itemwidget.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Ui {
|
||||
class ItemEncryptedSettings;
|
||||
}
|
||||
|
||||
class QIODevice;
|
||||
|
||||
class ItemEncrypted : public QWidget, public ItemWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ItemEncrypted(QWidget *parent);
|
||||
|
||||
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
|
||||
void setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||
const QModelIndex &index) const override;
|
||||
};
|
||||
|
||||
class ItemEncryptedSaver : public QObject, public ItemSaverInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
bool saveItems(const QString &tabName, const QAbstractItemModel &model, QIODevice *file) override;
|
||||
|
||||
signals:
|
||||
void error(const QString &);
|
||||
|
||||
private:
|
||||
void emitEncryptFailed();
|
||||
};
|
||||
|
||||
class ItemEncryptedScriptable : public ItemScriptable
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ItemEncryptedScriptable(QObject *parent) : ItemScriptable(parent) {}
|
||||
|
||||
public slots:
|
||||
bool isEncrypted();
|
||||
QByteArray encrypt();
|
||||
QByteArray decrypt();
|
||||
|
||||
void encryptItem();
|
||||
void decryptItem();
|
||||
|
||||
void encryptItems();
|
||||
void decryptItems();
|
||||
|
||||
void copyEncryptedItems();
|
||||
|
||||
QString generateTestKeys();
|
||||
bool isGpgInstalled();
|
||||
|
||||
private:
|
||||
QByteArray encrypt(const QByteArray &bytes);
|
||||
QByteArray decrypt(const QByteArray &bytes);
|
||||
};
|
||||
|
||||
class ItemEncryptedLoader : public QObject, public ItemLoaderInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID COPYQ_PLUGIN_ITEM_LOADER_ID)
|
||||
Q_INTERFACES(ItemLoaderInterface)
|
||||
|
||||
public:
|
||||
ItemEncryptedLoader();
|
||||
|
||||
~ItemEncryptedLoader();
|
||||
|
||||
ItemWidget *create(const QModelIndex &index, QWidget *parent, bool) const override;
|
||||
|
||||
QString id() const override { return "itemencrypted"; }
|
||||
QString name() const override { return tr("Encryption"); }
|
||||
QString author() const override { return QString(); }
|
||||
QString description() const override { return tr("Encrypt items and tabs."); }
|
||||
QVariant icon() const override { return QVariant(IconLock); }
|
||||
|
||||
QStringList formatsToSave() const override;
|
||||
|
||||
QVariantMap applySettings() override;
|
||||
|
||||
void loadSettings(const QVariantMap &settings) override { m_settings = settings; }
|
||||
|
||||
QWidget *createSettingsWidget(QWidget *parent) override;
|
||||
|
||||
bool canLoadItems(QIODevice *file) const override;
|
||||
|
||||
bool canSaveItems(const QString &tabName) const override;
|
||||
|
||||
ItemSaverPtr loadItems(const QString &tabName, QAbstractItemModel *model, QIODevice *file, int maxItems) override;
|
||||
|
||||
ItemSaverPtr initializeTab(const QString &, QAbstractItemModel *model, int maxItems) override;
|
||||
|
||||
QObject *tests(const TestInterfacePtr &test) const override;
|
||||
|
||||
const QObject *signaler() const override { return this; }
|
||||
|
||||
ItemScriptable *scriptableObject(QObject *parent) override;
|
||||
|
||||
QList<Command> commands() const override;
|
||||
|
||||
signals:
|
||||
void error(const QString &);
|
||||
void addCommands(const QList<Command> &commands);
|
||||
|
||||
private slots:
|
||||
void setPassword();
|
||||
void terminateGpgProcess();
|
||||
void onGpgProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void addCommands();
|
||||
|
||||
private:
|
||||
enum GpgProcessStatus {
|
||||
GpgNotInstalled,
|
||||
GpgNotRunning,
|
||||
GpgGeneratingKeys,
|
||||
GpgChangingPassword
|
||||
};
|
||||
|
||||
void updateUi();
|
||||
|
||||
void emitDecryptFailed();
|
||||
|
||||
ItemSaverPtr createSaver();
|
||||
|
||||
std::unique_ptr<Ui::ItemEncryptedSettings> ui;
|
||||
QVariantMap m_settings;
|
||||
|
||||
GpgProcessStatus m_gpgProcessStatus;
|
||||
QProcess *m_gpgProcess;
|
||||
};
|
||||
|
||||
#endif // ITEMENCRYPTED_H
|
@ -0,0 +1,23 @@
|
||||
include(../plugins_common.pri)
|
||||
|
||||
HEADERS += itemencrypted.h \
|
||||
../../src/gui/iconwidget.h
|
||||
SOURCES += itemencrypted.cpp
|
||||
SOURCES += \
|
||||
../../src/common/config.cpp \
|
||||
../../src/common/log.cpp \
|
||||
../../src/common/mimetypes.cpp \
|
||||
../../src/common/shortcuts.cpp \
|
||||
../../src/common/textdata.cpp \
|
||||
../../src/gui/iconfont.cpp \
|
||||
../../src/gui/iconwidget.cpp \
|
||||
../../src/item/serialize.cpp
|
||||
FORMS += itemencryptedsettings.ui
|
||||
|
||||
CONFIG(debug, debug|release) {
|
||||
SOURCES += tests/itemencryptedtests.cpp
|
||||
HEADERS += tests/itemencryptedtests.h
|
||||
}
|
||||
|
||||
TARGET = $$qtLibraryTarget(itemencrypted)
|
||||
|
@ -0,0 +1,160 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ItemEncryptedSettings</class>
|
||||
<widget class="QWidget" name="ItemEncryptedSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>324</width>
|
||||
<height>367</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>To encrypt and decrypt items add appropriate commands under Commands tab.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelInfo">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonPassword">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonAddCommands">
|
||||
<property name="text">
|
||||
<string>Add Actions to Menu and Toolbar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxShareInfo">
|
||||
<property name="title">
|
||||
<string>Sharing Encrypted Items and Tabs</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelShareInfo">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxEncryptTabs">
|
||||
<property name="title">
|
||||
<string>Encrypted Tabs</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string><p>Specify names of tabs (one per line) which will be automatically encrypted and decrypted.</p>
|
||||
<p>Set unload tab interval in History tab to safely unload decrypted items from memory.</p></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPlainTextEdit" name="plainTextEditEncryptTabs">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemencryptedtests.h"
|
||||
|
||||
#include "tests/test_utils.h"
|
||||
|
||||
ItemEncryptedTests::ItemEncryptedTests(const TestInterfacePtr &test, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_test(test)
|
||||
{
|
||||
}
|
||||
|
||||
void ItemEncryptedTests::initTestCase()
|
||||
{
|
||||
if ( qgetenv("COPYQ_TESTS_SKIP_ITEMENCRYPT") == "1" )
|
||||
SKIP("Unset COPYQ_TESTS_SKIP_ITEMENCRYPT to run the tests");
|
||||
|
||||
TEST(m_test->initTestCase());
|
||||
}
|
||||
|
||||
void ItemEncryptedTests::cleanupTestCase()
|
||||
{
|
||||
TEST(m_test->cleanupTestCase());
|
||||
}
|
||||
|
||||
void ItemEncryptedTests::init()
|
||||
{
|
||||
TEST(m_test->init());
|
||||
}
|
||||
|
||||
void ItemEncryptedTests::cleanup()
|
||||
{
|
||||
TEST( m_test->cleanup() );
|
||||
}
|
||||
|
||||
void ItemEncryptedTests::encryptDecryptData()
|
||||
{
|
||||
if ( !isGpgInstalled() )
|
||||
SKIP("gpg2 is required to run the test");
|
||||
|
||||
RUN("-e" << "plugins.itemencrypted.generateTestKeys()", "\n");
|
||||
|
||||
// Test gpg errors first.
|
||||
RUN("-e" << "plugins.itemencrypted.encrypt(input());print('')", "");
|
||||
|
||||
const QByteArray input("\x00\x01\x02\x03\x04", 5);
|
||||
QByteArray stdoutActual;
|
||||
QByteArray stderrActual;
|
||||
|
||||
// Encrypted data differs.
|
||||
QCOMPARE( m_test->run(Args("-e") << "plugins.itemencrypted.encrypt(input())", &stdoutActual, nullptr, input), 0 );
|
||||
QVERIFY(!stdoutActual.isEmpty());
|
||||
QVERIFY(stdoutActual != input);
|
||||
|
||||
QCOMPARE( m_test->run(Args("-e") << "plugins.itemencrypted.decrypt(plugins.itemencrypted.encrypt(input()))", &stdoutActual, nullptr, input), 0 );
|
||||
QCOMPARE(stdoutActual, input);
|
||||
}
|
||||
|
||||
bool ItemEncryptedTests::isGpgInstalled() const
|
||||
{
|
||||
QByteArray actualStdout;
|
||||
const auto exitCode = m_test->run(Args("-e") << "plugins.itemencrypted.isGpgInstalled()", &actualStdout);
|
||||
Q_ASSERT(exitCode == 0);
|
||||
Q_ASSERT(actualStdout == "true\n" || actualStdout == "false\n");
|
||||
return actualStdout == "true\n";
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMENCRYPTEDTESTS_H
|
||||
#define ITEMENCRYPTEDTESTS_H
|
||||
|
||||
#include "tests/testinterface.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class ItemEncryptedTests : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ItemEncryptedTests(const TestInterfacePtr &test, QObject *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
void encryptDecryptData();
|
||||
|
||||
private:
|
||||
bool isGpgInstalled() const;
|
||||
|
||||
TestInterfacePtr m_test;
|
||||
};
|
||||
|
||||
#endif // ITEMENCRYPTEDTESTS_H
|
@ -0,0 +1,42 @@
|
||||
file(GLOB copyq_plugin_itemfakevim_SOURCES
|
||||
${copyq_plugin_itemfakevim_SOURCES}
|
||||
fakevim/*.cpp
|
||||
fakevim/utils/*.cpp
|
||||
)
|
||||
|
||||
include_directories(fakevim)
|
||||
|
||||
add_definitions( -DFAKEVIM_STANDALONE -DQTCREATOR_UTILS_STATIC_LIB )
|
||||
set(copyq_plugin_itemfakevim_DEFINITIONS
|
||||
FAKEVIM_STANDALONE
|
||||
QTCREATOR_UTILS_STATIC_LIB)
|
||||
|
||||
set(copyq_plugin_itemfakevim_RESOURCES itemfakevim.qrc)
|
||||
|
||||
copyq_add_plugin(itemfakevim)
|
||||
|
||||
# Disable warnings for 3rd-party source code.
|
||||
if (PEDANTIC)
|
||||
if (CMAKE_COMPILER_IS_GNUCXX)
|
||||
set(IGNORE_PEDANTIC_FLAGS "-Wno-suggest-override")
|
||||
else()
|
||||
set(IGNORE_PEDANTIC_FLAGS "-Wno-unused-macros")
|
||||
endif()
|
||||
|
||||
set_source_files_properties(
|
||||
fakevim/fakevimhandler.cpp
|
||||
fakevim/fakevimactions.cpp
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
"\
|
||||
-Wno-shorten-64-to-32 \
|
||||
-Wno-sign-conversion \
|
||||
-Wno-conversion \
|
||||
-Wno-unreachable-code \
|
||||
-Wno-documentation-unknown-command \
|
||||
-Wno-shadow \
|
||||
-Wno-missing-declarations \
|
||||
-Wno-strict-overflow \
|
||||
${IGNORE_PEDANTIC_FLAGS} \
|
||||
")
|
||||
endif()
|
||||
|
@ -0,0 +1,22 @@
|
||||
Digia Qt LGPL Exception version 1.1
|
||||
|
||||
As an additional permission to the GNU Lesser General Public License version
|
||||
2.1, the object code form of a "work that uses the Library" may incorporate
|
||||
material from a header file that is part of the Library. You may distribute
|
||||
such object code under terms of your choice, provided that:
|
||||
(i) the header files of the Library have not been modified; and
|
||||
(ii) the incorporated material is limited to numerical parameters, data
|
||||
structure layouts, accessors, macros, inline functions and
|
||||
templates; and
|
||||
(iii) you comply with the terms of Section 6 of the GNU Lesser General
|
||||
Public License version 2.1.
|
||||
|
||||
Moreover, you may apply this exception to a modified version of the Library,
|
||||
provided that such modification does not involve copying material from the
|
||||
Library into the modified Library's header files unless such material is
|
||||
limited to (i) numerical parameters; (ii) data structure layouts;
|
||||
(iii) accessors; and (iv) small macros, templates and inline functions of
|
||||
five lines or less in length.
|
||||
|
||||
Furthermore, you are not required to apply this additional permission to a
|
||||
modified version of the Library.
|
After Width: | Height: | Size: 372 B |
@ -0,0 +1,14 @@
|
||||
include($$PWD/utils/utils.pri)
|
||||
|
||||
DEFINES += FAKEVIM_STANDALONE
|
||||
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
SOURCES += $$PWD/fakevimhandler.cpp \
|
||||
$$PWD/fakevimactions.cpp
|
||||
|
||||
HEADERS += $$PWD/fakevimhandler.h \
|
||||
$$PWD/fakevimactions.h
|
||||
|
||||
CONFIG += qt
|
||||
QT += widgets
|
@ -0,0 +1,3 @@
|
||||
TEMPLATE = lib
|
||||
|
||||
include(fakevim.pri)
|
@ -0,0 +1,236 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "fakevimactions.h"
|
||||
#include "fakevimhandler.h"
|
||||
|
||||
// Please do not add any direct dependencies to other Qt Creator code here.
|
||||
// Instead emit signals and let the FakeVimPlugin channel the information to
|
||||
// Qt Creator. The idea is to keep this file here in a "clean" state that
|
||||
// allows easy reuse with any QTextEdit or QPlainTextEdit derived class.
|
||||
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#ifdef FAKEVIM_STANDALONE
|
||||
using namespace FakeVim::Internal::Utils;
|
||||
#else
|
||||
using namespace Utils;
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FakeVimSettings
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace FakeVim {
|
||||
namespace Internal {
|
||||
|
||||
typedef QLatin1String _;
|
||||
|
||||
#ifdef FAKEVIM_STANDALONE
|
||||
namespace Utils {
|
||||
|
||||
SavedAction::SavedAction(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void SavedAction::setValue(const QVariant &value)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
QVariant SavedAction::value() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void SavedAction::setDefaultValue(const QVariant &value)
|
||||
{
|
||||
m_defaultValue = value;
|
||||
}
|
||||
|
||||
QVariant SavedAction::defaultValue() const
|
||||
{
|
||||
return m_defaultValue;
|
||||
}
|
||||
|
||||
void SavedAction::setSettingsKey(const QString &key)
|
||||
{
|
||||
m_settingsKey = key;
|
||||
}
|
||||
|
||||
QString SavedAction::settingsKey() const
|
||||
{
|
||||
return m_settingsKey;
|
||||
}
|
||||
|
||||
} // namespace Utils
|
||||
#endif // FAKEVIM_STANDALONE
|
||||
|
||||
FakeVimSettings::FakeVimSettings()
|
||||
{}
|
||||
|
||||
FakeVimSettings::~FakeVimSettings()
|
||||
{
|
||||
qDeleteAll(m_items);
|
||||
}
|
||||
|
||||
void FakeVimSettings::insertItem(int code, SavedAction *item,
|
||||
const QString &longName, const QString &shortName)
|
||||
{
|
||||
QTC_ASSERT(!m_items.contains(code), qDebug() << code; return);
|
||||
m_items[code] = item;
|
||||
if (!longName.isEmpty()) {
|
||||
m_nameToCode[longName] = code;
|
||||
m_codeToName[code] = longName;
|
||||
}
|
||||
if (!shortName.isEmpty())
|
||||
m_nameToCode[shortName] = code;
|
||||
}
|
||||
|
||||
#ifndef FAKEVIM_STANDALONE
|
||||
void FakeVimSettings::readSettings(QSettings *settings)
|
||||
{
|
||||
foreach (SavedAction *item, m_items)
|
||||
item->readSettings(settings);
|
||||
}
|
||||
|
||||
void FakeVimSettings::writeSettings(QSettings *settings)
|
||||
{
|
||||
foreach (SavedAction *item, m_items)
|
||||
item->writeSettings(settings);
|
||||
}
|
||||
#endif // FAKEVIM_STANDALONE
|
||||
|
||||
SavedAction *FakeVimSettings::item(int code)
|
||||
{
|
||||
QTC_ASSERT(m_items.value(code, 0), qDebug() << "CODE: " << code; return 0);
|
||||
return m_items.value(code, 0);
|
||||
}
|
||||
|
||||
SavedAction *FakeVimSettings::item(const QString &name)
|
||||
{
|
||||
return m_items.value(m_nameToCode.value(name, -1), 0);
|
||||
}
|
||||
|
||||
QString FakeVimSettings::trySetValue(const QString &name, const QString &value)
|
||||
{
|
||||
int code = m_nameToCode.value(name, -1);
|
||||
if (code == -1)
|
||||
return FakeVimHandler::tr("Unknown option: %1").arg(name);
|
||||
if (code == ConfigTabStop || code == ConfigShiftWidth) {
|
||||
if (value.toInt() <= 0)
|
||||
return FakeVimHandler::tr("Argument must be positive: %1=%2")
|
||||
.arg(name).arg(value);
|
||||
}
|
||||
SavedAction *act = item(code);
|
||||
if (!act)
|
||||
return FakeVimHandler::tr("Unknown option: %1").arg(name);
|
||||
act->setValue(value);
|
||||
return QString();
|
||||
}
|
||||
|
||||
SavedAction *createAction(FakeVimSettings *instance, int code, const QVariant &value,
|
||||
const QString &settingsKey = QString(),
|
||||
const QString &shortKey = QString())
|
||||
{
|
||||
SavedAction *item = new SavedAction(instance);
|
||||
item->setValue(value);
|
||||
#ifndef FAKEVIM_STANDALONE
|
||||
item->setSettingsKey(_("FakeVim"), settingsKey);
|
||||
item->setDefaultValue(value);
|
||||
item->setCheckable( value.canConvert<bool>() );
|
||||
#endif
|
||||
instance->insertItem(code, item, settingsKey.toLower(), shortKey);
|
||||
return item;
|
||||
}
|
||||
|
||||
FakeVimSettings *theFakeVimSettings()
|
||||
{
|
||||
static FakeVimSettings *s = 0;
|
||||
if (s)
|
||||
return s;
|
||||
|
||||
s = new FakeVimSettings;
|
||||
|
||||
// Specific FakeVim settings
|
||||
createAction(s, ConfigReadVimRc, false, _("ReadVimRc"));
|
||||
createAction(s, ConfigVimRcPath, QString(), _("VimRcPath"));
|
||||
#ifndef FAKEVIM_STANDALONE
|
||||
createAction(s, ConfigUseFakeVim, false, _("UseFakeVim"));
|
||||
s->item(ConfigUseFakeVim)->setText(QCoreApplication::translate("FakeVim::Internal",
|
||||
"Use Vim-style Editing"));
|
||||
s->item(ConfigReadVimRc)->setText(QCoreApplication::translate("FakeVim::Internal",
|
||||
"Read .vimrc"));
|
||||
s->item(ConfigVimRcPath)->setText(QCoreApplication::translate("FakeVim::Internal",
|
||||
"Path to .vimrc"));
|
||||
#endif
|
||||
createAction(s, ConfigShowMarks, false, _("ShowMarks"), _("sm"));
|
||||
createAction(s, ConfigPassControlKey, false, _("PassControlKey"), _("pck"));
|
||||
createAction(s, ConfigPassKeys, true, _("PassKeys"), _("pk"));
|
||||
|
||||
// Emulated Vim setting
|
||||
createAction(s, ConfigStartOfLine, true, _("StartOfLine"), _("sol"));
|
||||
createAction(s, ConfigTabStop, 8, _("TabStop"), _("ts"));
|
||||
createAction(s, ConfigSmartTab, false, _("SmartTab"), _("sta"));
|
||||
createAction(s, ConfigHlSearch, true, _("HlSearch"), _("hls"));
|
||||
createAction(s, ConfigShiftWidth, 8, _("ShiftWidth"), _("sw"));
|
||||
createAction(s, ConfigExpandTab, false, _("ExpandTab"), _("et"));
|
||||
createAction(s, ConfigAutoIndent, false, _("AutoIndent"), _("ai"));
|
||||
createAction(s, ConfigSmartIndent, false, _("SmartIndent"), _("si"));
|
||||
createAction(s, ConfigIncSearch, true, _("IncSearch"), _("is"));
|
||||
createAction(s, ConfigUseCoreSearch, false, _("UseCoreSearch"), _("ucs"));
|
||||
createAction(s, ConfigSmartCase, false, _("SmartCase"), _("scs"));
|
||||
createAction(s, ConfigIgnoreCase, false, _("IgnoreCase"), _("ic"));
|
||||
createAction(s, ConfigWrapScan, true, _("WrapScan"), _("ws"));
|
||||
createAction(s, ConfigTildeOp, false, _("TildeOp"), _("top"));
|
||||
createAction(s, ConfigShowCmd, true, _("ShowCmd"), _("sc"));
|
||||
createAction(s, ConfigRelativeNumber, false, _("RelativeNumber"),_("rnu"));
|
||||
createAction(s, ConfigScrollOff, 0, _("ScrollOff"), _("so"));
|
||||
createAction(s, ConfigBackspace, _("indent,eol,start"), _("ConfigBackspace"), _("bs"));
|
||||
createAction(s, ConfigIsKeyword, _("@,48-57,_,192-255,a-z,A-Z"), _("IsKeyword"), _("isk"));
|
||||
createAction(s, ConfigClipboard, QString(), _("Clipboard"), _("cb"));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
SavedAction *theFakeVimSetting(int code)
|
||||
{
|
||||
return theFakeVimSettings()->item(code);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace FakeVim
|
@ -0,0 +1,144 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef FAKEVIM_ACTIONS_H
|
||||
#define FAKEVIM_ACTIONS_H
|
||||
|
||||
#ifndef FAKEVIM_STANDALONE
|
||||
# include <utils/savedaction.h>
|
||||
#endif
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
namespace FakeVim {
|
||||
namespace Internal {
|
||||
|
||||
#ifdef FAKEVIM_STANDALONE
|
||||
namespace Utils {
|
||||
|
||||
class SavedAction : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SavedAction(QObject *parent);
|
||||
void setValue(const QVariant &value);
|
||||
QVariant value() const;
|
||||
void setDefaultValue(const QVariant &value);
|
||||
QVariant defaultValue() const;
|
||||
void setSettingsKey(const QString &key);
|
||||
QString settingsKey() const;
|
||||
|
||||
QVariant m_value;
|
||||
QVariant m_defaultValue;
|
||||
QString m_settingsKey;
|
||||
};
|
||||
|
||||
} // namespace Utils
|
||||
#endif // FAKEVIM_STANDALONE
|
||||
|
||||
enum FakeVimSettingsCode
|
||||
{
|
||||
ConfigUseFakeVim,
|
||||
ConfigReadVimRc,
|
||||
ConfigVimRcPath,
|
||||
|
||||
ConfigStartOfLine,
|
||||
ConfigHlSearch,
|
||||
ConfigTabStop,
|
||||
ConfigSmartTab,
|
||||
ConfigShiftWidth,
|
||||
ConfigExpandTab,
|
||||
ConfigAutoIndent,
|
||||
ConfigSmartIndent,
|
||||
|
||||
ConfigIncSearch,
|
||||
ConfigUseCoreSearch,
|
||||
ConfigSmartCase,
|
||||
ConfigIgnoreCase,
|
||||
ConfigWrapScan,
|
||||
|
||||
// command ~ behaves as g~
|
||||
ConfigTildeOp,
|
||||
|
||||
// indent allow backspacing over autoindent
|
||||
// eol allow backspacing over line breaks (join lines)
|
||||
// start allow backspacing over the start of insert; CTRL-W and CTRL-U
|
||||
// stop once at the start of insert.
|
||||
ConfigBackspace,
|
||||
|
||||
// @,48-57,_,192-255
|
||||
ConfigIsKeyword,
|
||||
|
||||
// other actions
|
||||
ConfigShowMarks,
|
||||
ConfigPassControlKey,
|
||||
ConfigPassKeys,
|
||||
ConfigClipboard,
|
||||
ConfigShowCmd,
|
||||
ConfigScrollOff,
|
||||
ConfigRelativeNumber
|
||||
};
|
||||
|
||||
class FakeVimSettings : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FakeVimSettings();
|
||||
~FakeVimSettings();
|
||||
void insertItem(int code, Utils::SavedAction *item,
|
||||
const QString &longname = QString(),
|
||||
const QString &shortname = QString());
|
||||
|
||||
Utils::SavedAction *item(int code);
|
||||
Utils::SavedAction *item(const QString &name);
|
||||
QString trySetValue(const QString &name, const QString &value);
|
||||
|
||||
#ifndef FAKEVIM_STANDALONE
|
||||
void readSettings(QSettings *settings);
|
||||
void writeSettings(QSettings *settings);
|
||||
#endif
|
||||
|
||||
private:
|
||||
QHash<int, Utils::SavedAction *> m_items;
|
||||
QHash<QString, int> m_nameToCode;
|
||||
QHash<int, QString> m_codeToName;
|
||||
};
|
||||
|
||||
FakeVimSettings *theFakeVimSettings();
|
||||
Utils::SavedAction *theFakeVimSetting(int code);
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace FakeVim
|
||||
|
||||
#endif // FAKEVIM_ACTTIONS_H
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,176 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef FAKEVIM_HANDLER_H
|
||||
#define FAKEVIM_HANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTextEdit>
|
||||
|
||||
namespace FakeVim {
|
||||
namespace Internal {
|
||||
|
||||
enum RangeMode
|
||||
{
|
||||
// Reordering first three enum items here will break
|
||||
// compatibility with clipboard format stored by Vim.
|
||||
RangeCharMode, // v
|
||||
RangeLineMode, // V
|
||||
RangeBlockMode, // Ctrl-v
|
||||
RangeLineModeExclusive,
|
||||
RangeBlockAndTailMode // Ctrl-v for D and X
|
||||
};
|
||||
|
||||
struct Range
|
||||
{
|
||||
Range();
|
||||
Range(int b, int e, RangeMode m = RangeCharMode);
|
||||
QString toString() const;
|
||||
bool isValid() const;
|
||||
|
||||
int beginPos;
|
||||
int endPos;
|
||||
RangeMode rangemode;
|
||||
};
|
||||
|
||||
struct ExCommand
|
||||
{
|
||||
ExCommand() : hasBang(false), count(1) {}
|
||||
ExCommand(const QString &cmd, const QString &args = QString(),
|
||||
const Range &range = Range());
|
||||
|
||||
bool matches(const QString &min, const QString &full) const;
|
||||
|
||||
QString cmd;
|
||||
bool hasBang;
|
||||
QString args;
|
||||
Range range;
|
||||
int count;
|
||||
};
|
||||
|
||||
// message levels sorted by severity
|
||||
enum MessageLevel
|
||||
{
|
||||
MessageMode, // show current mode (format "-- %1 --")
|
||||
MessageCommand, // show last Ex command or search
|
||||
MessageInfo, // result of a command
|
||||
MessageWarning, // warning
|
||||
MessageError, // error
|
||||
MessageShowCmd // partial command
|
||||
};
|
||||
|
||||
class FakeVimHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FakeVimHandler(QWidget *widget, QObject *parent = 0);
|
||||
~FakeVimHandler();
|
||||
|
||||
QWidget *widget();
|
||||
|
||||
// call before widget is deleted
|
||||
void disconnectFromEditor();
|
||||
|
||||
public slots:
|
||||
void setCurrentFileName(const QString &fileName);
|
||||
QString currentFileName() const;
|
||||
|
||||
void showMessage(MessageLevel level, const QString &msg);
|
||||
|
||||
// This executes an "ex" style command taking context
|
||||
// information from the current widget.
|
||||
void handleCommand(const QString &cmd);
|
||||
void handleReplay(const QString &keys);
|
||||
void handleInput(const QString &keys);
|
||||
|
||||
void installEventFilter();
|
||||
|
||||
// Convenience
|
||||
void setupWidget();
|
||||
void restoreWidget(int tabSize);
|
||||
|
||||
// Test only
|
||||
int physicalIndentation(const QString &line) const;
|
||||
int logicalIndentation(const QString &line) const;
|
||||
QString tabExpand(int n) const;
|
||||
|
||||
void miniBufferTextEdited(const QString &text, int cursorPos, int anchorPos);
|
||||
|
||||
// Set text cursor position. Keeps anchor if in visual mode.
|
||||
void setTextCursorPosition(int position);
|
||||
|
||||
QTextCursor textCursor() const;
|
||||
void setTextCursor(const QTextCursor &cursor);
|
||||
|
||||
bool jumpToLocalMark(QChar mark, bool backTickMode);
|
||||
|
||||
signals:
|
||||
void commandBufferChanged(const QString &msg, int cursorPos,
|
||||
int anchorPos, int messageLevel, QObject *eventFilter);
|
||||
void statusDataChanged(const QString &msg);
|
||||
void extraInformationChanged(const QString &msg);
|
||||
void selectionChanged(const QList<QTextEdit::ExtraSelection> &selection);
|
||||
void highlightMatches(const QString &needle);
|
||||
void writeAllRequested(QString *error);
|
||||
void moveToMatchingParenthesis(bool *moved, bool *forward, QTextCursor *cursor);
|
||||
void checkForElectricCharacter(bool *result, QChar c);
|
||||
void indentRegion(int beginLine, int endLine, QChar typedChar);
|
||||
void completionRequested();
|
||||
void simpleCompletionRequested(const QString &needle, bool forward);
|
||||
void windowCommandRequested(const QString &key, int count);
|
||||
void findRequested(bool reverse);
|
||||
void findNextRequested(bool reverse);
|
||||
void handleExCommandRequested(bool *handled, const ExCommand &cmd);
|
||||
void requestDisableBlockSelection();
|
||||
void requestSetBlockSelection(const QTextCursor&);
|
||||
void requestBlockSelection(QTextCursor*);
|
||||
void requestHasBlockSelection(bool *on);
|
||||
void foldToggle(int depth);
|
||||
void foldAll(bool fold);
|
||||
void fold(int depth, bool fold);
|
||||
void foldGoTo(int count, bool current);
|
||||
void jumpToGlobalMark(QChar mark, bool backTickMode, const QString &fileName);
|
||||
|
||||
public:
|
||||
class Private;
|
||||
|
||||
private:
|
||||
bool eventFilter(QObject *ob, QEvent *ev) override;
|
||||
|
||||
Private *d;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace FakeVim
|
||||
|
||||
Q_DECLARE_METATYPE(FakeVim::Internal::ExCommand)
|
||||
|
||||
|
||||
#endif // FAKEVIM_HANDLER_H
|
@ -0,0 +1,111 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef HOSTOSINFO_H
|
||||
#define HOSTOSINFO_H
|
||||
|
||||
#include "utils_global.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#define QTC_HOST_EXE_SUFFIX ".exe"
|
||||
#else
|
||||
#define QTC_HOST_EXE_SUFFIX ""
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
namespace Utils {
|
||||
|
||||
class QTCREATOR_UTILS_EXPORT HostOsInfo
|
||||
{
|
||||
public:
|
||||
// Add more as needed.
|
||||
enum HostOs { HostOsWindows, HostOsLinux, HostOsMac, HostOsOtherUnix, HostOsOther };
|
||||
static inline HostOs hostOs();
|
||||
|
||||
enum HostArchitecture { HostArchitectureX86, HostArchitectureAMD64, HostArchitectureItanium,
|
||||
HostArchitectureArm, HostArchitectureUnknown };
|
||||
static HostArchitecture hostArchitecture();
|
||||
|
||||
static bool isWindowsHost() { return hostOs() == HostOsWindows; }
|
||||
static bool isLinuxHost() { return hostOs() == HostOsLinux; }
|
||||
static bool isMacHost() { return hostOs() == HostOsMac; }
|
||||
static inline bool isAnyUnixHost();
|
||||
|
||||
static QString withExecutableSuffix(const QString &executable)
|
||||
{
|
||||
QString finalName = executable;
|
||||
if (isWindowsHost())
|
||||
finalName += QLatin1String(QTC_HOST_EXE_SUFFIX);
|
||||
return finalName;
|
||||
}
|
||||
|
||||
static Qt::CaseSensitivity fileNameCaseSensitivity()
|
||||
{
|
||||
return isWindowsHost() ? Qt::CaseInsensitive: Qt::CaseSensitive;
|
||||
}
|
||||
|
||||
static QChar pathListSeparator()
|
||||
{
|
||||
return isWindowsHost() ? QLatin1Char(';') : QLatin1Char(':');
|
||||
}
|
||||
|
||||
static Qt::KeyboardModifier controlModifier()
|
||||
{
|
||||
return isMacHost() ? Qt::MetaModifier : Qt::ControlModifier;
|
||||
}
|
||||
};
|
||||
|
||||
HostOsInfo::HostOs HostOsInfo::hostOs()
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
return HostOsWindows;
|
||||
#elif defined(Q_OS_LINUX)
|
||||
return HostOsLinux;
|
||||
#elif defined(Q_OS_MAC)
|
||||
return HostOsMac;
|
||||
#elif defined(Q_OS_UNIX)
|
||||
return HostOsOtherUnix;
|
||||
#else
|
||||
return HostOsOther;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool HostOsInfo::isAnyUnixHost()
|
||||
{
|
||||
#ifdef Q_OS_UNIX
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Utils
|
||||
|
||||
#endif // HOSTOSINFO_H
|
@ -0,0 +1,39 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qtcassert.h"
|
||||
|
||||
namespace Utils {
|
||||
|
||||
void writeAssertLocation(const char *msg)
|
||||
{
|
||||
qDebug("SOFT ASSERT: %s", msg);
|
||||
}
|
||||
|
||||
} // namespace Utils
|
@ -0,0 +1,49 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QTC_ASSERT_H
|
||||
#define QTC_ASSERT_H
|
||||
|
||||
#include "utils_global.h"
|
||||
|
||||
namespace Utils { QTCREATOR_UTILS_EXPORT void writeAssertLocation(const char *msg); }
|
||||
|
||||
#define QTC_ASSERT_STRINGIFY_HELPER(x) #x
|
||||
#define QTC_ASSERT_STRINGIFY(x) QTC_ASSERT_STRINGIFY_HELPER(x)
|
||||
#define QTC_ASSERT_STRING(cond) ::Utils::writeAssertLocation(\
|
||||
"\"" cond"\" in file " __FILE__ ", line " QTC_ASSERT_STRINGIFY(__LINE__))
|
||||
|
||||
// The 'do {...} while (0)' idiom is not used for the main block here to be
|
||||
// able to use 'break' and 'continue' as 'actions'.
|
||||
|
||||
#define QTC_ASSERT(cond, action) if (cond) {} else { QTC_ASSERT_STRING(#cond); action; } do {} while (0)
|
||||
#define QTC_CHECK(cond) if (cond) {} else { QTC_ASSERT_STRING(#cond); } do {} while (0)
|
||||
|
||||
#endif // QTC_ASSERT_H
|
||||
|
@ -0,0 +1,3 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
SOURCES += $$PWD/qtcassert.cpp
|
@ -0,0 +1,43 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef UTILS_GLOBAL_H
|
||||
#define UTILS_GLOBAL_H
|
||||
|
||||
#include <qglobal.h>
|
||||
|
||||
#if defined(QTCREATOR_UTILS_LIB)
|
||||
# define QTCREATOR_UTILS_EXPORT Q_DECL_EXPORT
|
||||
#elif defined(QTCREATOR_UTILS_STATIC_LIB) // Abuse single files for manual tests
|
||||
# define QTCREATOR_UTILS_EXPORT
|
||||
#else
|
||||
# define QTCREATOR_UTILS_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
|
||||
#endif // UTILS_GLOBAL_H
|
@ -0,0 +1,608 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemfakevim.h"
|
||||
#include "ui_itemfakevimsettings.h"
|
||||
|
||||
#include "tests/itemfakevimtests.h"
|
||||
#include "common/contenttype.h"
|
||||
|
||||
#include "fakevim/fakevimhandler.h"
|
||||
|
||||
using namespace FakeVim::Internal;
|
||||
|
||||
#include <QIcon>
|
||||
#include <QMessageBox>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QStatusBar>
|
||||
#include <QTextBlock>
|
||||
#include <QTextEdit>
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QScrollBar>
|
||||
#include <QtPlugin>
|
||||
|
||||
namespace {
|
||||
|
||||
class TextEditWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TextEditWidget(QTextEdit *editor, QWidget *parent = nullptr)
|
||||
: QWidget(parent)
|
||||
, m_textEdit(editor)
|
||||
, m_handler(new FakeVimHandler(editor, nullptr))
|
||||
, m_hasBlockSelection(false)
|
||||
{
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
layout->addWidget(editor);
|
||||
|
||||
setFocusProxy(editor);
|
||||
|
||||
m_handler->installEventFilter();
|
||||
m_handler->setupWidget();
|
||||
|
||||
connect( editor, SIGNAL(selectionChanged()),
|
||||
this, SLOT(onSelectionChanged()) );
|
||||
connect( editor, SIGNAL(cursorPositionChanged()),
|
||||
this, SLOT(onSelectionChanged()) );
|
||||
|
||||
setLineWrappingEnabled(true);
|
||||
|
||||
editor->viewport()->installEventFilter(this);
|
||||
|
||||
editor->setStyleSheet("QTextEdit{background:transparent}");
|
||||
}
|
||||
|
||||
~TextEditWidget()
|
||||
{
|
||||
m_handler->disconnectFromEditor();
|
||||
m_handler->deleteLater();
|
||||
}
|
||||
|
||||
bool eventFilter(QObject *, QEvent *ev) override
|
||||
{
|
||||
if ( ev->type() != QEvent::Paint )
|
||||
return false;
|
||||
|
||||
QWidget *viewport = editor()->viewport();
|
||||
|
||||
QPaintEvent *e = static_cast<QPaintEvent*>(ev);
|
||||
|
||||
const QRect r = e->rect();
|
||||
|
||||
QPainter painter(viewport);
|
||||
|
||||
const QTextCursor tc = editor()->textCursor();
|
||||
|
||||
m_context.cursorPosition = -1;
|
||||
m_context.palette = palette();
|
||||
|
||||
const int h = horizontalOffset();
|
||||
const int v = verticalOffset();
|
||||
m_context.clip = r.translated(h, v);
|
||||
|
||||
painter.save();
|
||||
|
||||
// Draw base and text.
|
||||
painter.translate(-h, -v);
|
||||
paintDocument(&painter);
|
||||
|
||||
// Draw block selection.
|
||||
if ( hasBlockSelection() ) {
|
||||
QRect rect;
|
||||
QTextCursor tc2 = tc;
|
||||
tc2.setPosition(tc.position());
|
||||
rect = editor()->cursorRect(tc2);
|
||||
tc2.setPosition(tc.anchor());
|
||||
rect = rect.united( editor()->cursorRect(tc2) );
|
||||
|
||||
m_context.palette.setColor(QPalette::Base, m_context.palette.color(QPalette::Highlight));
|
||||
m_context.palette.setColor(QPalette::Text, m_context.palette.color(QPalette::HighlightedText));
|
||||
|
||||
m_context.clip = rect.translated(h, v);
|
||||
|
||||
paintDocument(&painter);
|
||||
}
|
||||
|
||||
painter.restore();
|
||||
|
||||
// Draw text cursor.
|
||||
QRect rect = editor()->cursorRect();
|
||||
|
||||
if (editor()->overwriteMode() || hasBlockSelection() ) {
|
||||
QFontMetrics fm(font());
|
||||
QChar c = editor()->document()->characterAt( tc.position() );
|
||||
rect.setWidth( fm.width(c) );
|
||||
if (rect.width() == 0)
|
||||
rect.setWidth( fm.averageCharWidth() );
|
||||
} else {
|
||||
rect.setWidth(2);
|
||||
rect.adjust(-1, 0, 0, 0);
|
||||
}
|
||||
|
||||
if ( hasBlockSelection() ) {
|
||||
int from = tc.positionInBlock();
|
||||
int to = tc.anchor() - tc.document()->findBlock(tc.anchor()).position();
|
||||
if (from > to)
|
||||
rect.moveLeft(rect.left() - rect.width());
|
||||
}
|
||||
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Difference);
|
||||
painter.fillRect(rect, Qt::white);
|
||||
|
||||
if (!hasBlockSelection() && m_cursorRect.width() != rect.width())
|
||||
viewport->update();
|
||||
|
||||
m_cursorRect = rect;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FakeVimHandler &fakeVimHandler() { return *m_handler; }
|
||||
|
||||
void highlightMatches(const QString &pattern)
|
||||
{
|
||||
QTextCursor cur = editor()->textCursor();
|
||||
|
||||
Selection selection;
|
||||
selection.format.setBackground(Qt::yellow);
|
||||
selection.format.setForeground(Qt::black);
|
||||
|
||||
// Highlight matches.
|
||||
QTextDocument *doc = editor()->document();
|
||||
QRegExp re(pattern);
|
||||
cur = doc->find(re);
|
||||
|
||||
m_searchSelection.clear();
|
||||
|
||||
int a = cur.position();
|
||||
while ( !cur.isNull() ) {
|
||||
if ( cur.hasSelection() ) {
|
||||
selection.cursor = cur;
|
||||
m_searchSelection.append(selection);
|
||||
} else {
|
||||
cur.movePosition(QTextCursor::NextCharacter);
|
||||
}
|
||||
cur = doc->find(re, cur);
|
||||
int b = cur.position();
|
||||
if (a == b) {
|
||||
cur.movePosition(QTextCursor::NextCharacter);
|
||||
cur = doc->find(re, cur);
|
||||
b = cur.position();
|
||||
if (a == b) break;
|
||||
}
|
||||
a = b;
|
||||
}
|
||||
|
||||
updateSelections();
|
||||
}
|
||||
|
||||
void setBlockSelection(bool on)
|
||||
{
|
||||
m_hasBlockSelection = on;
|
||||
m_selection.clear();
|
||||
updateSelections();
|
||||
}
|
||||
|
||||
bool hasBlockSelection() const
|
||||
{
|
||||
return m_hasBlockSelection;
|
||||
}
|
||||
|
||||
QTextEdit *editor() const { return m_textEdit; }
|
||||
|
||||
void setLineWrappingEnabled(bool enable)
|
||||
{
|
||||
editor()->setLineWrapMode(enable ? QTextEdit::WidgetWidth : QTextEdit::NoWrap);
|
||||
}
|
||||
|
||||
private slots:
|
||||
void onSelectionChanged() {
|
||||
m_hasBlockSelection = false;
|
||||
m_selection.clear();
|
||||
|
||||
Selection selection;
|
||||
|
||||
const QPalette pal = palette();
|
||||
selection.format.setBackground( pal.color(QPalette::Highlight) );
|
||||
selection.format.setForeground( pal.color(QPalette::HighlightedText) );
|
||||
selection.cursor = editor()->textCursor();
|
||||
if ( selection.cursor.hasSelection() )
|
||||
m_selection.append(selection);
|
||||
|
||||
updateSelections();
|
||||
}
|
||||
|
||||
private:
|
||||
int horizontalOffset() const
|
||||
{
|
||||
QScrollBar *hbar = editor()->horizontalScrollBar();
|
||||
return isRightToLeft() ? (hbar->maximum() - hbar->value()) : hbar->value();
|
||||
}
|
||||
|
||||
int verticalOffset() const
|
||||
{
|
||||
return editor()->verticalScrollBar()->value();
|
||||
}
|
||||
|
||||
void paintDocument(QPainter *painter)
|
||||
{
|
||||
painter->setClipRect(m_context.clip);
|
||||
painter->fillRect(m_context.clip, m_context.palette.base());
|
||||
editor()->document()->documentLayout()->draw(painter, m_context);
|
||||
}
|
||||
|
||||
void updateSelections()
|
||||
{
|
||||
m_context.selections.clear();
|
||||
m_context.selections.reserve( m_searchSelection.size() + m_selection.size() );
|
||||
m_context.selections << m_searchSelection << m_selection;
|
||||
editor()->viewport()->update();
|
||||
}
|
||||
|
||||
QTextEdit *m_textEdit;
|
||||
FakeVimHandler *m_handler;
|
||||
QRect m_cursorRect;
|
||||
|
||||
bool m_hasBlockSelection;
|
||||
|
||||
using Selection = QAbstractTextDocumentLayout::Selection;
|
||||
using SelectionList = QVector<Selection>;
|
||||
SelectionList m_searchSelection;
|
||||
SelectionList m_selection;
|
||||
|
||||
QAbstractTextDocumentLayout::PaintContext m_context;
|
||||
};
|
||||
|
||||
class Proxy : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Proxy(TextEditWidget *editorWidget, QStatusBar *statusBar, QObject *parent = nullptr)
|
||||
: QObject(parent), m_editorWidget(editorWidget), m_statusBar(statusBar)
|
||||
{}
|
||||
|
||||
public slots:
|
||||
void changeStatusData(const QString &info)
|
||||
{
|
||||
m_statusData = info;
|
||||
updateStatusBar();
|
||||
}
|
||||
|
||||
void highlightMatches(const QString &pattern)
|
||||
{
|
||||
m_editorWidget->highlightMatches(pattern);
|
||||
}
|
||||
|
||||
void changeStatusMessage(const QString &contents, int cursorPos)
|
||||
{
|
||||
m_statusMessage = cursorPos == -1 ? contents
|
||||
: contents.left(cursorPos) + QChar(10073) + contents.mid(cursorPos);
|
||||
updateStatusBar();
|
||||
}
|
||||
|
||||
void changeExtraInformation(const QString &info)
|
||||
{
|
||||
QMessageBox::information(m_editorWidget, tr("Information"), info);
|
||||
}
|
||||
|
||||
void updateStatusBar()
|
||||
{
|
||||
int slack = 80 - m_statusMessage.size() - m_statusData.size();
|
||||
QString msg = m_statusMessage + QString(slack, QLatin1Char(' ')) + m_statusData;
|
||||
m_statusBar->showMessage(msg);
|
||||
}
|
||||
|
||||
void handleExCommand(bool *handled, const ExCommand &cmd)
|
||||
{
|
||||
if (cmd.cmd == "set") {
|
||||
QString arg = cmd.args;
|
||||
bool enable = !arg.startsWith("no");
|
||||
if (enable)
|
||||
arg.remove(0, 2);
|
||||
*handled = setOption(arg, enable);
|
||||
} else if ( wantSaveAndQuit(cmd) ) {
|
||||
// :wq
|
||||
emit save();
|
||||
emit cancel();
|
||||
} else if ( wantSave(cmd) ) {
|
||||
emit save(); // :w
|
||||
} else if ( wantQuit(cmd) ) {
|
||||
if (cmd.hasBang)
|
||||
emit invalidate(); // :q!
|
||||
else
|
||||
emit cancel(); // :q
|
||||
} else {
|
||||
*handled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
*handled = true;
|
||||
}
|
||||
|
||||
void requestSetBlockSelection(const QTextCursor &cursor)
|
||||
{
|
||||
m_editorWidget->editor()->setTextCursor(cursor);
|
||||
m_editorWidget->setBlockSelection(true);
|
||||
}
|
||||
|
||||
void requestDisableBlockSelection()
|
||||
{
|
||||
m_editorWidget->setBlockSelection(false);
|
||||
}
|
||||
|
||||
void requestBlockSelection(QTextCursor *cursor)
|
||||
{
|
||||
*cursor = m_editorWidget->editor()->textCursor();
|
||||
m_editorWidget->setBlockSelection(true);
|
||||
}
|
||||
|
||||
signals:
|
||||
void save();
|
||||
void cancel();
|
||||
void invalidate();
|
||||
|
||||
private:
|
||||
bool wantSaveAndQuit(const ExCommand &cmd)
|
||||
{
|
||||
return cmd.cmd == "wq";
|
||||
}
|
||||
|
||||
bool wantSave(const ExCommand &cmd)
|
||||
{
|
||||
return cmd.matches("w", "write") || cmd.matches("wa", "wall");
|
||||
}
|
||||
|
||||
bool wantQuit(const ExCommand &cmd)
|
||||
{
|
||||
return cmd.matches("q", "quit") || cmd.matches("qa", "qall");
|
||||
}
|
||||
|
||||
bool setOption(const QString &option, bool enable)
|
||||
{
|
||||
if (option == "linebreak" || option == "lbr")
|
||||
m_editorWidget->setLineWrappingEnabled(enable);
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
TextEditWidget *m_editorWidget;
|
||||
QStatusBar *m_statusBar;
|
||||
QString m_statusMessage;
|
||||
QString m_statusData;
|
||||
};
|
||||
|
||||
class Editor : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Editor(QTextEdit *editor, const QString &sourceFileName, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_editor(new TextEditWidget(editor, this))
|
||||
{
|
||||
m_editor->editor()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
||||
// Create status bar.
|
||||
m_statusBar = new QStatusBar(this);
|
||||
|
||||
// Connect slots to FakeVimHandler signals.
|
||||
auto proxy = new Proxy(m_editor, m_statusBar, this);
|
||||
connectSignals( &m_editor->fakeVimHandler(), proxy );
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->addWidget(m_editor);
|
||||
layout->addWidget(m_statusBar);
|
||||
setFocusProxy(m_editor);
|
||||
|
||||
if (!sourceFileName.isEmpty())
|
||||
m_editor->fakeVimHandler().handleCommand("source " + sourceFileName);
|
||||
}
|
||||
|
||||
TextEditWidget *textEditWidget() { return m_editor; }
|
||||
|
||||
signals:
|
||||
void save();
|
||||
void cancel();
|
||||
void invalidate();
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override
|
||||
{
|
||||
if (event->type() == QEvent::PaletteChange) {
|
||||
QPalette pal = palette();
|
||||
m_editor->setPalette(pal);
|
||||
pal.setColor(QPalette::Window, pal.color(QPalette::Base));
|
||||
pal.setColor(QPalette::WindowText, pal.color(QPalette::Text));
|
||||
m_statusBar->setPalette(pal);
|
||||
} else if (event->type() == QEvent::FontChange) {
|
||||
m_editor->setFont(font());
|
||||
m_editor->editor()->setFont(font());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void connectSignals(FakeVimHandler *handler, Proxy *proxy)
|
||||
{
|
||||
connect(handler, SIGNAL(commandBufferChanged(QString,int,int,int,QObject*)),
|
||||
proxy, SLOT(changeStatusMessage(QString,int)));
|
||||
connect(handler, SIGNAL(extraInformationChanged(QString)),
|
||||
proxy, SLOT(changeExtraInformation(QString)));
|
||||
connect(handler, SIGNAL(statusDataChanged(QString)),
|
||||
proxy, SLOT(changeStatusData(QString)));
|
||||
connect(handler, SIGNAL(highlightMatches(QString)),
|
||||
proxy, SLOT(highlightMatches(QString)));
|
||||
connect(handler, SIGNAL(handleExCommandRequested(bool*,ExCommand)),
|
||||
proxy, SLOT(handleExCommand(bool*,ExCommand)));
|
||||
connect(handler, SIGNAL(requestSetBlockSelection(QTextCursor)),
|
||||
proxy, SLOT(requestSetBlockSelection(QTextCursor)));
|
||||
connect(handler, SIGNAL(requestDisableBlockSelection()),
|
||||
proxy, SLOT(requestDisableBlockSelection()));
|
||||
connect(handler, SIGNAL(requestBlockSelection(QTextCursor*)),
|
||||
proxy, SLOT(requestBlockSelection(QTextCursor*)));
|
||||
|
||||
connect(proxy, SIGNAL(save()), SIGNAL(save()));
|
||||
connect(proxy, SIGNAL(cancel()), SIGNAL(cancel()));
|
||||
connect(proxy, SIGNAL(invalidate()), SIGNAL(invalidate()));
|
||||
}
|
||||
|
||||
TextEditWidget *m_editor;
|
||||
QStatusBar *m_statusBar;
|
||||
};
|
||||
|
||||
QWidget *getItemEditorWidget(QWidget *editor)
|
||||
{
|
||||
Editor *ed = qobject_cast<Editor*>(editor);
|
||||
return ed ? ed->textEditWidget()->editor() : editor;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemFakeVim::ItemFakeVim(ItemWidget *childItem, const QString &sourceFileName)
|
||||
: ItemWidget(childItem->widget())
|
||||
, m_childItem(childItem)
|
||||
, m_sourceFileName(sourceFileName)
|
||||
{
|
||||
}
|
||||
|
||||
void ItemFakeVim::setCurrent(bool current)
|
||||
{
|
||||
m_childItem->setCurrent(current);
|
||||
}
|
||||
|
||||
void ItemFakeVim::highlight(const QRegExp &re, const QFont &highlightFont, const QPalette &highlightPalette)
|
||||
{
|
||||
m_childItem->setHighlight(re, highlightFont, highlightPalette);
|
||||
}
|
||||
|
||||
void ItemFakeVim::updateSize(const QSize &maximumSize, int idealWidth)
|
||||
{
|
||||
m_childItem->updateSize(maximumSize, idealWidth);
|
||||
}
|
||||
|
||||
QWidget *ItemFakeVim::createEditor(QWidget *parent) const
|
||||
{
|
||||
QWidget *editor = m_childItem->createEditor(parent);
|
||||
QTextEdit *textEdit = qobject_cast<QTextEdit *>(editor);
|
||||
if (textEdit)
|
||||
return new Editor(textEdit, m_sourceFileName, parent);
|
||||
return editor;
|
||||
}
|
||||
|
||||
void ItemFakeVim::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
m_childItem->setEditorData( getItemEditorWidget(editor), index );
|
||||
|
||||
// Position text cursor at the begining of text instead of selecting all.
|
||||
auto ed = qobject_cast<Editor*>(editor);
|
||||
if (ed) {
|
||||
auto textEdit = ed->textEditWidget()->editor();
|
||||
textEdit->setTextCursor( QTextCursor(textEdit->document()) );
|
||||
}
|
||||
}
|
||||
|
||||
void ItemFakeVim::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
|
||||
{
|
||||
m_childItem->setModelData( getItemEditorWidget(editor), model, index );
|
||||
}
|
||||
|
||||
bool ItemFakeVim::hasChanges(QWidget *editor) const
|
||||
{
|
||||
return m_childItem->hasChanges( getItemEditorWidget(editor) );
|
||||
|
||||
}
|
||||
|
||||
QObject *ItemFakeVim::createExternalEditor(const QModelIndex &index, QWidget *parent) const
|
||||
{
|
||||
return m_childItem->createExternalEditor(index, parent);
|
||||
}
|
||||
|
||||
void ItemFakeVim::setTagged(bool tagged)
|
||||
{
|
||||
return m_childItem->setTagged(tagged);
|
||||
}
|
||||
|
||||
ItemFakeVimLoader::ItemFakeVimLoader()
|
||||
: m_enabled(false)
|
||||
{
|
||||
}
|
||||
|
||||
ItemFakeVimLoader::~ItemFakeVimLoader() = default;
|
||||
|
||||
QVariant ItemFakeVimLoader::icon() const
|
||||
{
|
||||
return QIcon(":/fakevim/fakevim.png");
|
||||
}
|
||||
|
||||
QVariantMap ItemFakeVimLoader::applySettings()
|
||||
{
|
||||
QVariantMap settings;
|
||||
settings["really_enable"] = m_enabled = ui->checkBoxEnable->isChecked();
|
||||
settings["source_file"] = m_sourceFileName = ui->lineEditSourceFileName->text();
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
void ItemFakeVimLoader::loadSettings(const QVariantMap &settings)
|
||||
{
|
||||
m_enabled = settings.value("really_enable", false).toBool();
|
||||
m_sourceFileName = settings.value("source_file").toString();
|
||||
}
|
||||
|
||||
QWidget *ItemFakeVimLoader::createSettingsWidget(QWidget *parent)
|
||||
{
|
||||
ui.reset(new Ui::ItemFakeVimSettings);
|
||||
QWidget *w = new QWidget(parent);
|
||||
ui->setupUi(w);
|
||||
|
||||
ui->checkBoxEnable->setChecked(m_enabled);
|
||||
ui->lineEditSourceFileName->setText(m_sourceFileName);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
ItemWidget *ItemFakeVimLoader::transform(ItemWidget *itemWidget, const QModelIndex &)
|
||||
{
|
||||
return m_enabled ? new ItemFakeVim(itemWidget, m_sourceFileName) : nullptr;
|
||||
}
|
||||
|
||||
QObject *ItemFakeVimLoader::tests(const TestInterfacePtr &test) const
|
||||
{
|
||||
#ifdef HAS_TESTS
|
||||
QVariantMap settings;
|
||||
settings["really_enable"] = true;
|
||||
settings["source_file"] = QString(ItemFakeVimTests::fileNameToSource());
|
||||
QObject *tests = new ItemFakeVimTests(test);
|
||||
tests->setProperty("CopyQ_test_settings", settings);
|
||||
return tests;
|
||||
#else
|
||||
Q_UNUSED(test);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
Q_EXPORT_PLUGIN2(itemfakevim, ItemFakeVimLoader)
|
||||
|
||||
#include "itemfakevim.moc"
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMFAKEVIM_H
|
||||
#define ITEMFAKEVIM_H
|
||||
|
||||
#include "item/itemwidget.h"
|
||||
|
||||
#include <QScopedPointer>
|
||||
|
||||
namespace Ui {
|
||||
class ItemFakeVimSettings;
|
||||
}
|
||||
|
||||
class QWidget;
|
||||
|
||||
class ItemFakeVim : public ItemWidget
|
||||
{
|
||||
public:
|
||||
ItemFakeVim(ItemWidget *childItem, const QString &sourceFileName);
|
||||
|
||||
void setCurrent(bool current) override;
|
||||
|
||||
protected:
|
||||
void highlight(const QRegExp &re, const QFont &highlightFont,
|
||||
const QPalette &highlightPalette) override;
|
||||
|
||||
void updateSize(const QSize &maximumSize, int idealWidth) override;
|
||||
|
||||
QWidget *createEditor(QWidget *parent) const override;
|
||||
|
||||
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
|
||||
void setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
bool hasChanges(QWidget *editor) const override;
|
||||
|
||||
QObject *createExternalEditor(const QModelIndex &index, QWidget *parent) const override;
|
||||
|
||||
void setTagged(bool tagged) override;
|
||||
|
||||
private:
|
||||
QScopedPointer<ItemWidget> m_childItem;
|
||||
QString m_sourceFileName;
|
||||
};
|
||||
|
||||
class ItemFakeVimLoader : public QObject, public ItemLoaderInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID COPYQ_PLUGIN_ITEM_LOADER_ID)
|
||||
Q_INTERFACES(ItemLoaderInterface)
|
||||
|
||||
public:
|
||||
ItemFakeVimLoader();
|
||||
~ItemFakeVimLoader();
|
||||
|
||||
QString id() const override { return "itemfakevim"; }
|
||||
QString name() const override { return tr("FakeVim"); }
|
||||
QString author() const override
|
||||
{ return tr("FakeVim plugin is part of Qt Creator")
|
||||
+ " (Copyright (C) override 2013 Digia Plc and/or its subsidiary(-ies))."; }
|
||||
QString description() const override { return tr("Emulate Vim editor while editing items."); }
|
||||
QVariant icon() const override;
|
||||
|
||||
QVariantMap applySettings() override;
|
||||
|
||||
void loadSettings(const QVariantMap &settings) override;
|
||||
|
||||
QWidget *createSettingsWidget(QWidget *parent) override;
|
||||
|
||||
ItemWidget *transform(ItemWidget *itemWidget, const QModelIndex &index) override;
|
||||
|
||||
QObject *tests(const TestInterfacePtr &test) const override;
|
||||
|
||||
private:
|
||||
bool m_enabled;
|
||||
QString m_sourceFileName;
|
||||
QScopedPointer<Ui::ItemFakeVimSettings> ui;
|
||||
};
|
||||
|
||||
#endif // ITEMFAKEVIM_H
|
@ -0,0 +1,16 @@
|
||||
include(../plugins_common.pri)
|
||||
|
||||
HEADERS += itemfakevim.h
|
||||
SOURCES += itemfakevim.cpp
|
||||
FORMS += itemfakevimsettings.ui
|
||||
TARGET = $$qtLibraryTarget(itemfakevim)
|
||||
RESOURCES += itemfakevim.qrc
|
||||
DEFINES += QTCREATOR_UTILS_STATIC_LIB
|
||||
|
||||
CONFIG(debug, debug|release) {
|
||||
SOURCES += tests/itemfakevimtests.cpp
|
||||
HEADERS += tests/itemfakevimtests.h
|
||||
}
|
||||
|
||||
include(fakevim/fakevim.pri)
|
||||
|
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>fakevim/fakevim.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ItemFakeVimSettings</class>
|
||||
<widget class="QWidget" name="ItemFakeVimSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxEnable">
|
||||
<property name="text">
|
||||
<string>Enable FakeVim for Editing Items</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Path to Configuration File:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEditSourceFileName"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemfakevimtests.h"
|
||||
|
||||
#include "tests/test_utils.h"
|
||||
|
||||
#include <QDir>
|
||||
|
||||
ItemFakeVimTests::ItemFakeVimTests(const TestInterfacePtr &test, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_test(test)
|
||||
{
|
||||
}
|
||||
|
||||
QString ItemFakeVimTests::fileNameToSource()
|
||||
{
|
||||
return QDir::tempPath() + "/itemfakevim.rc";
|
||||
}
|
||||
|
||||
void ItemFakeVimTests::initTestCase()
|
||||
{
|
||||
TEST(m_test->initTestCase());
|
||||
}
|
||||
|
||||
void ItemFakeVimTests::cleanupTestCase()
|
||||
{
|
||||
TEST(m_test->cleanupTestCase());
|
||||
}
|
||||
|
||||
void ItemFakeVimTests::init()
|
||||
{
|
||||
TEST(m_test->init());
|
||||
|
||||
// Don't use default external editor.
|
||||
RUN("config" << "editor" << "", "\n");
|
||||
}
|
||||
|
||||
void ItemFakeVimTests::cleanup()
|
||||
{
|
||||
TEST( m_test->cleanup() );
|
||||
}
|
||||
|
||||
void ItemFakeVimTests::createItem()
|
||||
{
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "tab" << tab1;
|
||||
|
||||
RUN(args << "size", "0\n");
|
||||
|
||||
RUN(args << "edit", "");
|
||||
RUN(args << "keys" << ":iABC" << "ENTER" << ":DEF"
|
||||
<< "ESC" << "::wq" << "ENTER", "");
|
||||
|
||||
RUN(args << "read" << "0", "ABC\nDEF");
|
||||
|
||||
SKIP("Command :w saves item and the editor widget is destroyed because data changed.");
|
||||
RUN(args << "keys" << "F2" << ":GccXYZ" << "ESC" << "::w" << "ENTER", "");
|
||||
RUN(args << "read" << "0", "ABC\nXYZ");
|
||||
RUN(args << "keys" << ":p:wq" << "ENTER", "");
|
||||
RUN(args << "read" << "0", "ABC\nXYZ\nDEF");
|
||||
}
|
||||
|
||||
void ItemFakeVimTests::blockSelection()
|
||||
{
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "tab" << tab1;
|
||||
|
||||
RUN(args << "edit", "");
|
||||
RUN(args << "keys"
|
||||
<< ":iABC" << "ENTER" << ":DEF" << "ENTER" << ":GHI" << "ESC" << "::wq" << "ENTER", "");
|
||||
RUN(args << "read" << "0", "ABC\nDEF\nGHI");
|
||||
|
||||
RUN(args << "edit" << "0", "");
|
||||
RUN(args << "keys"
|
||||
<< ":ggl" << "CTRL+V" << ":jjs_" << "ESC" << "::wq" << "ENTER", "");
|
||||
RUN(args << "read" << "0", "A_C\nD_F\nG_I");
|
||||
}
|
||||
|
||||
void ItemFakeVimTests::search()
|
||||
{
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "tab" << tab1;
|
||||
|
||||
RUN(args << "edit", "");
|
||||
RUN(args << "keys"
|
||||
<< ":iABC" << "ENTER" << ":DEF" << "ENTER" << ":GHI" << "ESC" << "::wq" << "ENTER", "");
|
||||
RUN(args << "read" << "0", "ABC\nDEF\nGHI");
|
||||
|
||||
RUN(args << "edit" << "0", "");
|
||||
RUN(args << "keys"
|
||||
<< ":gg/[EH]" << "ENTER" << ":r_nr_" << "F2", "");
|
||||
RUN(args << "read" << "0", "ABC\nD_F\nG_I");
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMFAKEVIMTESTS_H
|
||||
#define ITEMFAKEVIMTESTS_H
|
||||
|
||||
#include "tests/testinterface.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class ItemFakeVimTests : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ItemFakeVimTests(const TestInterfacePtr &test, QObject *parent = nullptr);
|
||||
|
||||
static QString fileNameToSource();
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
void createItem();
|
||||
|
||||
void blockSelection();
|
||||
|
||||
void search();
|
||||
|
||||
private:
|
||||
TestInterfacePtr m_test;
|
||||
};
|
||||
|
||||
#endif // ITEMFAKEVIMTESTS_H
|
@ -0,0 +1,8 @@
|
||||
set(copyq_plugin_itemimage_SOURCES
|
||||
../../src/common/log.cpp
|
||||
../../src/common/mimetypes.cpp
|
||||
../../src/item/itemeditor.cpp
|
||||
)
|
||||
|
||||
copyq_add_plugin(itemimage)
|
||||
|
@ -0,0 +1,237 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemimage.h"
|
||||
#include "ui_itemimagesettings.h"
|
||||
|
||||
#include "common/contenttype.h"
|
||||
#include "item/itemeditor.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QHBoxLayout>
|
||||
#include <QModelIndex>
|
||||
#include <QMovie>
|
||||
#include <QPixmap>
|
||||
#include <QtPlugin>
|
||||
#include <QVariant>
|
||||
|
||||
namespace {
|
||||
|
||||
QString findImageFormat(const QList<QString> &formats)
|
||||
{
|
||||
// Check formats in this order.
|
||||
static const QStringList imageFormats = QStringList()
|
||||
<< QString("image/png")
|
||||
<< QString("image/bmp")
|
||||
<< QString("image/jpeg")
|
||||
<< QString("image/gif")
|
||||
<< QString("image/svg+xml");
|
||||
|
||||
for (const auto &format : imageFormats) {
|
||||
if ( formats.contains(format) )
|
||||
return format;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool getImageData(const QModelIndex &index, QByteArray *data, QString *mime)
|
||||
{
|
||||
QVariantMap dataMap = index.data(contentType::data).toMap();
|
||||
|
||||
*mime = findImageFormat(dataMap.keys());
|
||||
if ( mime->isEmpty() )
|
||||
return false;
|
||||
|
||||
*data = dataMap[*mime].toByteArray();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getAnimatedImageData(const QModelIndex &index, QByteArray *data, QByteArray *format)
|
||||
{
|
||||
QVariantMap dataMap = index.data(contentType::data).toMap();
|
||||
|
||||
for (const auto &movieFormat : QMovie::supportedFormats()) {
|
||||
const QByteArray mime = "image/" + movieFormat;
|
||||
if (dataMap.contains(mime)) {
|
||||
*format = movieFormat;
|
||||
*data = dataMap[mime].toByteArray();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool getPixmapFromData(const QModelIndex &index, QPixmap *pix)
|
||||
{
|
||||
QString mime;
|
||||
QByteArray data;
|
||||
if ( !getImageData(index, &data, &mime) )
|
||||
return false;
|
||||
|
||||
pix->loadFromData( data, mime.toLatin1() );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemImage::ItemImage(
|
||||
const QPixmap &pix,
|
||||
const QByteArray &animationData, const QByteArray &animationFormat,
|
||||
const QString &imageEditor, const QString &svgEditor,
|
||||
QWidget *parent)
|
||||
: QLabel(parent)
|
||||
, ItemWidget(this)
|
||||
, m_editor(imageEditor)
|
||||
, m_svgEditor(svgEditor)
|
||||
, m_pixmap(pix)
|
||||
, m_animationData(animationData)
|
||||
, m_animationFormat(animationFormat)
|
||||
, m_animation(nullptr)
|
||||
{
|
||||
setMargin(4);
|
||||
setPixmap(pix);
|
||||
}
|
||||
|
||||
QObject *ItemImage::createExternalEditor(const QModelIndex &index, QWidget *parent) const
|
||||
{
|
||||
QString mime;
|
||||
QByteArray data;
|
||||
if ( !getImageData(index, &data, &mime) )
|
||||
return nullptr;
|
||||
|
||||
const QString &cmd = mime.contains("svg") ? m_svgEditor : m_editor;
|
||||
|
||||
return cmd.isEmpty() ? nullptr : new ItemEditor(data, mime, cmd, parent);
|
||||
}
|
||||
|
||||
void ItemImage::setCurrent(bool current)
|
||||
{
|
||||
if (current) {
|
||||
if ( !m_animationData.isEmpty() ) {
|
||||
if (!m_animation) {
|
||||
QBuffer *stream = new QBuffer(&m_animationData, this);
|
||||
m_animation = new QMovie(stream, m_animationFormat, this);
|
||||
m_animation->setScaledSize( m_pixmap.size() );
|
||||
}
|
||||
|
||||
if (m_animation) {
|
||||
setMovie(m_animation);
|
||||
startAnimation();
|
||||
m_animation->start();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stopAnimation();
|
||||
setPixmap(m_pixmap);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemImage::showEvent(QShowEvent *event)
|
||||
{
|
||||
startAnimation();
|
||||
QLabel::showEvent(event);
|
||||
}
|
||||
|
||||
void ItemImage::hideEvent(QHideEvent *event)
|
||||
{
|
||||
QLabel::hideEvent(event);
|
||||
stopAnimation();
|
||||
}
|
||||
|
||||
void ItemImage::startAnimation()
|
||||
{
|
||||
if (movie())
|
||||
movie()->start();
|
||||
}
|
||||
|
||||
void ItemImage::stopAnimation()
|
||||
{
|
||||
if (movie())
|
||||
movie()->stop();
|
||||
}
|
||||
|
||||
ItemImageLoader::ItemImageLoader()
|
||||
{
|
||||
}
|
||||
|
||||
ItemImageLoader::~ItemImageLoader() = default;
|
||||
|
||||
ItemWidget *ItemImageLoader::create(const QModelIndex &index, QWidget *parent, bool preview) const
|
||||
{
|
||||
if ( index.data(contentType::isHidden).toBool() )
|
||||
return nullptr;
|
||||
|
||||
// TODO: Just check if image provided and load it in different thread.
|
||||
QPixmap pix;
|
||||
if ( !getPixmapFromData(index, &pix) )
|
||||
return nullptr;
|
||||
|
||||
// scale pixmap
|
||||
const int w = preview ? 0 : m_settings.value("max_image_width", 320).toInt();
|
||||
const int h = preview ? 0 : m_settings.value("max_image_height", 240).toInt();
|
||||
if ( w > 0 && pix.width() > w && (h <= 0 || pix.width()/w > pix.height()/h) ) {
|
||||
pix = pix.scaledToWidth(w);
|
||||
} else if (h > 0 && pix.height() > h) {
|
||||
pix = pix.scaledToHeight(h);
|
||||
}
|
||||
|
||||
QByteArray animationData;
|
||||
QByteArray animationFormat;
|
||||
getAnimatedImageData(index, &animationData, &animationFormat);
|
||||
|
||||
return new ItemImage(pix,
|
||||
animationData, animationFormat,
|
||||
m_settings.value("image_editor").toString(),
|
||||
m_settings.value("svg_editor").toString(), parent);
|
||||
}
|
||||
|
||||
QStringList ItemImageLoader::formatsToSave() const
|
||||
{
|
||||
return QStringList()
|
||||
<< QString("image/svg+xml")
|
||||
<< QString("image/png")
|
||||
<< QString("image/gif");
|
||||
}
|
||||
|
||||
QVariantMap ItemImageLoader::applySettings()
|
||||
{
|
||||
m_settings["max_image_width"] = ui->spinBoxImageWidth->value();
|
||||
m_settings["max_image_height"] = ui->spinBoxImageHeight->value();
|
||||
m_settings["image_editor"] = ui->lineEditImageEditor->text();
|
||||
m_settings["svg_editor"] = ui->lineEditSvgEditor->text();
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
QWidget *ItemImageLoader::createSettingsWidget(QWidget *parent)
|
||||
{
|
||||
ui.reset(new Ui::ItemImageSettings);
|
||||
QWidget *w = new QWidget(parent);
|
||||
ui->setupUi(w);
|
||||
ui->spinBoxImageWidth->setValue( m_settings.value("max_image_width", 320).toInt() );
|
||||
ui->spinBoxImageHeight->setValue( m_settings.value("max_image_height", 240).toInt() );
|
||||
ui->lineEditImageEditor->setText( m_settings.value("image_editor", "").toString() );
|
||||
ui->lineEditSvgEditor->setText( m_settings.value("svg_editor", "").toString() );
|
||||
return w;
|
||||
}
|
||||
|
||||
Q_EXPORT_PLUGIN2(itemimage, ItemImageLoader)
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMIMAGE_H
|
||||
#define ITEMIMAGE_H
|
||||
|
||||
#include "gui/icons.h"
|
||||
#include "item/itemwidget.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QMovie;
|
||||
|
||||
namespace Ui {
|
||||
class ItemImageSettings;
|
||||
}
|
||||
|
||||
class ItemImage : public QLabel, public ItemWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ItemImage(
|
||||
const QPixmap &pix,
|
||||
const QByteArray &animationData, const QByteArray &animationFormat,
|
||||
const QString &imageEditor, const QString &svgEditor,
|
||||
QWidget *parent);
|
||||
|
||||
QWidget *createEditor(QWidget *) const override { return nullptr; }
|
||||
|
||||
QObject *createExternalEditor(const QModelIndex &index, QWidget *parent) const override;
|
||||
|
||||
void setCurrent(bool current) override;
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
|
||||
private:
|
||||
void startAnimation();
|
||||
void stopAnimation();
|
||||
|
||||
QString m_editor;
|
||||
QString m_svgEditor;
|
||||
QPixmap m_pixmap;
|
||||
QByteArray m_animationData;
|
||||
QByteArray m_animationFormat;
|
||||
QMovie *m_animation;
|
||||
};
|
||||
|
||||
class ItemImageLoader : public QObject, public ItemLoaderInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID COPYQ_PLUGIN_ITEM_LOADER_ID)
|
||||
Q_INTERFACES(ItemLoaderInterface)
|
||||
|
||||
public:
|
||||
ItemImageLoader();
|
||||
~ItemImageLoader();
|
||||
|
||||
ItemWidget *create(const QModelIndex &index, QWidget *parent, bool preview) const override;
|
||||
|
||||
int priority() const override { return 15; }
|
||||
|
||||
QString id() const override { return "itemimage"; }
|
||||
QString name() const override { return tr("Images"); }
|
||||
QString author() const override { return QString(); }
|
||||
QString description() const override { return tr("Display images."); }
|
||||
QVariant icon() const override { return QVariant(IconCamera); }
|
||||
|
||||
QStringList formatsToSave() const override;
|
||||
|
||||
QVariantMap applySettings() override;
|
||||
|
||||
void loadSettings(const QVariantMap &settings) override { m_settings = settings; }
|
||||
|
||||
QWidget *createSettingsWidget(QWidget *parent) override;
|
||||
|
||||
private:
|
||||
QVariantMap m_settings;
|
||||
std::unique_ptr<Ui::ItemImageSettings> ui;
|
||||
};
|
||||
|
||||
#endif // ITEMIMAGE_H
|
@ -0,0 +1,13 @@
|
||||
include(../plugins_common.pri)
|
||||
|
||||
HEADERS += \
|
||||
itemimage.h \
|
||||
../../src/item/itemeditor.h
|
||||
SOURCES += \
|
||||
itemimage.cpp \
|
||||
../../src/item/itemeditor.cpp \
|
||||
../../src/common/log.cpp \
|
||||
../../src/common/mimetypes.cpp
|
||||
FORMS += itemimagesettings.ui
|
||||
TARGET = $$qtLibraryTarget(itemimage)
|
||||
|
@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ItemImageSettings</class>
|
||||
<widget class="QWidget" name="ItemImageSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>327</width>
|
||||
<height>208</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Maximum Image &Width:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>spinBoxImageWidth</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinBoxImageWidth">
|
||||
<property name="toolTip">
|
||||
<string>Maximum width of image displayed in history (set to zero for original size)</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>4096</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Maximum Image &Height:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>spinBoxImageHeight</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0">
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinBoxImageHeight">
|
||||
<property name="toolTip">
|
||||
<string>Maximum height of image displayed in history (set to zero for original size)</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>4096</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Image editor command:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditImageEditor</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="lineEditImageEditor">
|
||||
<property name="toolTip">
|
||||
<string>Editor command for supported image formats other than SVG.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>&SVG editor command:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lineEditSvgEditor</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEditSvgEditor">
|
||||
<property name="toolTip">
|
||||
<string>Editor command for SVG image format.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>spinBoxImageWidth</tabstop>
|
||||
<tabstop>spinBoxImageHeight</tabstop>
|
||||
<tabstop>lineEditImageEditor</tabstop>
|
||||
<tabstop>lineEditSvgEditor</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,7 @@
|
||||
set(copyq_plugin_itemnotes_SOURCES
|
||||
../../src/gui/iconfont.cpp
|
||||
../../src/gui/iconwidget.cpp
|
||||
)
|
||||
|
||||
copyq_add_plugin(itemnotes)
|
||||
|
@ -0,0 +1,341 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemnotes.h"
|
||||
#include "ui_itemnotessettings.h"
|
||||
|
||||
#include "common/contenttype.h"
|
||||
#include "gui/iconfont.h"
|
||||
#include "gui/iconwidget.h"
|
||||
|
||||
#include <QBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QModelIndex>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocument>
|
||||
#include <QTextEdit>
|
||||
#include <QTimer>
|
||||
#include <QToolTip>
|
||||
#include <QtPlugin>
|
||||
#include <QDebug>
|
||||
|
||||
namespace {
|
||||
|
||||
// Limit number of characters for performance reasons.
|
||||
const int defaultMaxBytes = 10*1024;
|
||||
|
||||
const char mimeNotes[] = "application/x-copyq-item-notes";
|
||||
const char mimeIcon[] = "application/x-copyq-item-icon";
|
||||
|
||||
const int notesIndent = 16;
|
||||
|
||||
QWidget *createIconWidget(const QByteArray &icon, QWidget *parent)
|
||||
{
|
||||
if (!icon.isEmpty()) {
|
||||
QPixmap p;
|
||||
if (p.loadFromData(icon)) {
|
||||
const int side = iconFontSizePixels() + 2;
|
||||
p = p.scaled(side, side, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
QLabel *label = new QLabel(parent);
|
||||
const int m = side / 4;
|
||||
label->setContentsMargins(m, m, m, m);
|
||||
label->setPixmap(p);
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
return new IconWidget(IconEditSign, parent);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemNotes::ItemNotes(ItemWidget *childItem, const QString &text, const QByteArray &icon,
|
||||
bool notesAtBottom, bool showIconOnly, bool showToolTip)
|
||||
: QWidget( childItem->widget()->parentWidget() )
|
||||
, ItemWidget(this)
|
||||
, m_notes(nullptr)
|
||||
, m_icon(nullptr)
|
||||
, m_childItem(childItem)
|
||||
, m_notesAtBottom(notesAtBottom)
|
||||
, m_timerShowToolTip(nullptr)
|
||||
, m_toolTipText()
|
||||
{
|
||||
m_childItem->widget()->setObjectName("item_child");
|
||||
m_childItem->widget()->setParent(this);
|
||||
|
||||
if (showIconOnly || !icon.isEmpty())
|
||||
m_icon = createIconWidget(icon, this);
|
||||
|
||||
if (!showIconOnly)
|
||||
m_notes = new QTextEdit(this);
|
||||
|
||||
QBoxLayout *layout;
|
||||
|
||||
if (showIconOnly) {
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->addWidget(m_icon, 0, Qt::AlignRight | Qt::AlignTop);
|
||||
layout->addWidget(m_childItem->widget());
|
||||
} else {
|
||||
m_notes->setObjectName("item_child");
|
||||
m_notes->setProperty("CopyQ_item_type", "notes");
|
||||
|
||||
m_notes->setReadOnly(true);
|
||||
m_notes->setUndoRedoEnabled(false);
|
||||
|
||||
m_notes->setFocusPolicy(Qt::NoFocus);
|
||||
m_notes->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_notes->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_notes->setFrameStyle(QFrame::NoFrame);
|
||||
m_notes->setContextMenuPolicy(Qt::NoContextMenu);
|
||||
|
||||
m_notes->viewport()->installEventFilter(this);
|
||||
|
||||
m_notes->setPlainText( text.left(defaultMaxBytes) );
|
||||
|
||||
layout = new QVBoxLayout(this);
|
||||
|
||||
auto labelLayout = new QHBoxLayout;
|
||||
labelLayout->setMargin(0);
|
||||
labelLayout->setContentsMargins(notesIndent, 0, 0, 0);
|
||||
|
||||
if (m_icon)
|
||||
labelLayout->addWidget(m_icon, 0, Qt::AlignLeft);
|
||||
|
||||
labelLayout->addWidget(m_notes, 1, Qt::AlignLeft);
|
||||
|
||||
if (notesAtBottom) {
|
||||
layout->addWidget( m_childItem->widget() );
|
||||
layout->addLayout(labelLayout);
|
||||
} else {
|
||||
layout->addLayout(labelLayout);
|
||||
layout->addWidget( m_childItem->widget() );
|
||||
}
|
||||
}
|
||||
|
||||
if (showToolTip) {
|
||||
m_timerShowToolTip = new QTimer(this);
|
||||
m_timerShowToolTip->setInterval(250);
|
||||
m_timerShowToolTip->setSingleShot(true);
|
||||
connect( m_timerShowToolTip, SIGNAL(timeout()),
|
||||
this, SLOT(showToolTip()) );
|
||||
m_toolTipText = text;
|
||||
}
|
||||
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
}
|
||||
|
||||
void ItemNotes::setCurrent(bool current)
|
||||
{
|
||||
ItemWidget::setCurrent(current);
|
||||
|
||||
if (m_timerShowToolTip == nullptr)
|
||||
return;
|
||||
|
||||
QToolTip::hideText();
|
||||
|
||||
if (current)
|
||||
m_timerShowToolTip->start();
|
||||
else
|
||||
m_timerShowToolTip->stop();
|
||||
}
|
||||
|
||||
void ItemNotes::highlight(const QRegExp &re, const QFont &highlightFont, const QPalette &highlightPalette)
|
||||
{
|
||||
m_childItem->setHighlight(re, highlightFont, highlightPalette);
|
||||
|
||||
if (m_notes != nullptr) {
|
||||
QList<QTextEdit::ExtraSelection> selections;
|
||||
|
||||
if ( !re.isEmpty() ) {
|
||||
QTextEdit::ExtraSelection selection;
|
||||
selection.format.setBackground( highlightPalette.base() );
|
||||
selection.format.setForeground( highlightPalette.text() );
|
||||
selection.format.setFont(highlightFont);
|
||||
|
||||
QTextCursor cur = m_notes->document()->find(re);
|
||||
int a = cur.position();
|
||||
while ( !cur.isNull() ) {
|
||||
if ( cur.hasSelection() ) {
|
||||
selection.cursor = cur;
|
||||
selections.append(selection);
|
||||
} else {
|
||||
cur.movePosition(QTextCursor::NextCharacter);
|
||||
}
|
||||
cur = m_notes->document()->find(re, cur);
|
||||
int b = cur.position();
|
||||
if (a == b) {
|
||||
cur.movePosition(QTextCursor::NextCharacter);
|
||||
cur = m_notes->document()->find(re, cur);
|
||||
b = cur.position();
|
||||
if (a == b) break;
|
||||
}
|
||||
a = b;
|
||||
}
|
||||
}
|
||||
|
||||
m_notes->setExtraSelections(selections);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
QWidget *ItemNotes::createEditor(QWidget *parent) const
|
||||
{
|
||||
return (m_childItem == nullptr) ? nullptr : m_childItem->createEditor(parent);
|
||||
}
|
||||
|
||||
void ItemNotes::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
Q_ASSERT(m_childItem != nullptr);
|
||||
return m_childItem->setEditorData(editor, index);
|
||||
}
|
||||
|
||||
void ItemNotes::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
|
||||
{
|
||||
Q_ASSERT(m_childItem != nullptr);
|
||||
return m_childItem->setModelData(editor, model, index);
|
||||
}
|
||||
|
||||
bool ItemNotes::hasChanges(QWidget *editor) const
|
||||
{
|
||||
Q_ASSERT(m_childItem != nullptr);
|
||||
return m_childItem->hasChanges(editor);
|
||||
}
|
||||
|
||||
QObject *ItemNotes::createExternalEditor(const QModelIndex &index, QWidget *parent) const
|
||||
{
|
||||
return m_childItem ? m_childItem->createExternalEditor(index, parent)
|
||||
: ItemWidget::createExternalEditor(index, parent);
|
||||
}
|
||||
|
||||
void ItemNotes::updateSize(const QSize &maximumSize, int idealWidth)
|
||||
{
|
||||
setMaximumSize(maximumSize);
|
||||
|
||||
if (m_notes) {
|
||||
const int w = maximumSize.width() - 2 * notesIndent - 8;
|
||||
QTextDocument *doc = m_notes->document();
|
||||
doc->setTextWidth(w);
|
||||
m_notes->setFixedSize(
|
||||
static_cast<int>(doc->idealWidth()) + 16,
|
||||
static_cast<int>(doc->size().height()) );
|
||||
}
|
||||
|
||||
if (m_childItem != nullptr)
|
||||
m_childItem->updateSize(maximumSize, idealWidth);
|
||||
|
||||
adjustSize();
|
||||
setFixedSize(sizeHint());
|
||||
}
|
||||
|
||||
void ItemNotes::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QWidget::paintEvent(event);
|
||||
|
||||
// Decorate notes.
|
||||
if (m_notes != nullptr) {
|
||||
QPainter p(this);
|
||||
|
||||
QColor c = p.pen().color();
|
||||
c.setAlpha(80);
|
||||
p.setBrush(c);
|
||||
p.setPen(Qt::NoPen);
|
||||
QWidget *w = m_icon ? m_icon : m_notes;
|
||||
p.drawRect(w->x() - notesIndent + 4, w->y() + 4,
|
||||
notesIndent - 4, m_notes->height() - 8);
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemNotes::eventFilter(QObject *, QEvent *event)
|
||||
{
|
||||
return ItemWidget::filterMouseEvents(m_notes, event);
|
||||
}
|
||||
|
||||
void ItemNotes::showToolTip()
|
||||
{
|
||||
QToolTip::hideText();
|
||||
|
||||
QPoint toolTipPosition = QPoint(parentWidget()->contentsRect().width() - 16, height() - 16);
|
||||
toolTipPosition = mapToGlobal(toolTipPosition);
|
||||
|
||||
QToolTip::showText(toolTipPosition, m_toolTipText, this);
|
||||
}
|
||||
|
||||
ItemNotesLoader::ItemNotesLoader()
|
||||
{
|
||||
}
|
||||
|
||||
ItemNotesLoader::~ItemNotesLoader() = default;
|
||||
|
||||
QStringList ItemNotesLoader::formatsToSave() const
|
||||
{
|
||||
return QStringList() << mimeNotes << mimeIcon;
|
||||
}
|
||||
|
||||
QVariantMap ItemNotesLoader::applySettings()
|
||||
{
|
||||
m_settings["notes_at_bottom"] = ui->radioButtonBottom->isChecked();
|
||||
m_settings["icon_only"] = ui->radioButtonIconOnly->isChecked();
|
||||
m_settings["show_tooltip"] = ui->checkBoxShowToolTip->isChecked();
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
QWidget *ItemNotesLoader::createSettingsWidget(QWidget *parent)
|
||||
{
|
||||
ui.reset(new Ui::ItemNotesSettings);
|
||||
QWidget *w = new QWidget(parent);
|
||||
ui->setupUi(w);
|
||||
|
||||
if ( m_settings["icon_only"].toBool() )
|
||||
ui->radioButtonIconOnly->setChecked(true);
|
||||
else if ( m_settings["notes_at_bottom"].toBool() )
|
||||
ui->radioButtonBottom->setChecked(true);
|
||||
else
|
||||
ui->radioButtonTop->setChecked(true);
|
||||
|
||||
ui->checkBoxShowToolTip->setChecked( m_settings["show_tooltip"].toBool() );
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
ItemWidget *ItemNotesLoader::transform(ItemWidget *itemWidget, const QModelIndex &index)
|
||||
{
|
||||
const QString text = index.data(contentType::notes).toString();
|
||||
if ( text.isEmpty() )
|
||||
return nullptr;
|
||||
|
||||
const QByteArray icon = index.data(contentType::data).toMap().value(mimeIcon).toByteArray();
|
||||
|
||||
itemWidget->setTagged(true);
|
||||
return new ItemNotes( itemWidget, text, icon,
|
||||
m_settings["notes_at_bottom"].toBool(),
|
||||
m_settings["icon_only"].toBool(),
|
||||
m_settings["show_tooltip"].toBool() );
|
||||
}
|
||||
|
||||
bool ItemNotesLoader::matches(const QModelIndex &index, const QRegExp &re) const
|
||||
{
|
||||
const QString text = index.data(contentType::notes).toString();
|
||||
return re.indexIn(text) != -1;
|
||||
}
|
||||
|
||||
Q_EXPORT_PLUGIN2(itemnotes, ItemNotesLoader)
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMNOTES_H
|
||||
#define ITEMNOTES_H
|
||||
|
||||
#include "gui/icons.h"
|
||||
#include "item/itemwidget.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Ui {
|
||||
class ItemNotesSettings;
|
||||
}
|
||||
|
||||
class QTextEdit;
|
||||
class QTimer;
|
||||
|
||||
class ItemNotes : public QWidget, public ItemWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ItemNotes(ItemWidget *childItem, const QString &text, const QByteArray &icon,
|
||||
bool notesAtBottom, bool showIconOnly, bool showToolTip);
|
||||
|
||||
void setCurrent(bool current) override;
|
||||
|
||||
protected:
|
||||
void highlight(const QRegExp &re, const QFont &highlightFont,
|
||||
const QPalette &highlightPalette) override;
|
||||
|
||||
QWidget *createEditor(QWidget *parent) const override;
|
||||
|
||||
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
|
||||
void setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
bool hasChanges(QWidget *editor) const override;
|
||||
|
||||
QObject *createExternalEditor(const QModelIndex &index, QWidget *parent) const override;
|
||||
|
||||
void updateSize(const QSize &maximumSize, int idealWidth) override;
|
||||
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
bool eventFilter(QObject *, QEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void showToolTip();
|
||||
|
||||
private:
|
||||
QTextEdit *m_notes;
|
||||
QWidget *m_icon;
|
||||
std::unique_ptr<ItemWidget> m_childItem;
|
||||
bool m_notesAtBottom;
|
||||
QTimer *m_timerShowToolTip;
|
||||
QString m_toolTipText;
|
||||
};
|
||||
|
||||
class ItemNotesLoader : public QObject, public ItemLoaderInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID COPYQ_PLUGIN_ITEM_LOADER_ID)
|
||||
Q_INTERFACES(ItemLoaderInterface)
|
||||
|
||||
public:
|
||||
ItemNotesLoader();
|
||||
~ItemNotesLoader();
|
||||
|
||||
QString id() const override { return "itemnotes"; }
|
||||
QString name() const override { return tr("Notes"); }
|
||||
QString author() const override { return QString(); }
|
||||
QString description() const override { return tr("Display notes for items."); }
|
||||
QVariant icon() const override { return QVariant(IconEditSign); }
|
||||
|
||||
QStringList formatsToSave() const override;
|
||||
|
||||
QVariantMap applySettings() override;
|
||||
|
||||
void loadSettings(const QVariantMap &settings) override { m_settings = settings; }
|
||||
|
||||
QWidget *createSettingsWidget(QWidget *parent) override;
|
||||
|
||||
ItemWidget *transform(ItemWidget *itemWidget, const QModelIndex &index) override;
|
||||
|
||||
bool matches(const QModelIndex &index, const QRegExp &re) const override;
|
||||
|
||||
private:
|
||||
QVariantMap m_settings;
|
||||
std::unique_ptr<Ui::ItemNotesSettings> ui;
|
||||
};
|
||||
|
||||
#endif // ITEMNOTES_H
|
@ -0,0 +1,10 @@
|
||||
include(../plugins_common.pri)
|
||||
|
||||
HEADERS += itemnotes.h \
|
||||
../../src/gui/iconwidget.h
|
||||
SOURCES += itemnotes.cpp \
|
||||
../../src/gui/iconfont.cpp \
|
||||
../../src/gui/iconwidget.cpp
|
||||
FORMS += itemnotessettings.ui
|
||||
TARGET = $$qtLibraryTarget(itemnotes)
|
||||
|
@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ItemNotesSettings</class>
|
||||
<widget class="QWidget" name="ItemNotesSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Notes Position</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButtonTop">
|
||||
<property name="text">
|
||||
<string>A&bove Item</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButtonBottom">
|
||||
<property name="text">
|
||||
<string>B&elow Item</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButtonIconOnly">
|
||||
<property name="text">
|
||||
<string>&Icon Only</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxShowToolTip">
|
||||
<property name="text">
|
||||
<string>Show Too&l Tip</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,6 @@
|
||||
set(copyq_plugin_itempinned_SOURCES
|
||||
../../src/common/display.cpp
|
||||
)
|
||||
|
||||
copyq_add_plugin(itempinned)
|
||||
|
@ -0,0 +1,408 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itempinned.h"
|
||||
#include "ui_itempinnedsettings.h"
|
||||
|
||||
#include "common/command.h"
|
||||
#include "common/contenttype.h"
|
||||
#include "common/display.h"
|
||||
|
||||
#ifdef HAS_TESTS
|
||||
# include "tests/itempinnedtests.h"
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QModelIndex>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
const char mimePinned[] = "application/x-copyq-item-pinned";
|
||||
|
||||
bool isPinned(const QModelIndex &index)
|
||||
{
|
||||
const auto dataMap = index.data(contentType::data).toMap();
|
||||
return dataMap.contains(mimePinned);
|
||||
}
|
||||
|
||||
Command dummyPinCommand()
|
||||
{
|
||||
Command c;
|
||||
c.icon = QString(QChar(IconThumbTack));
|
||||
c.inMenu = true;
|
||||
c.shortcuts = QStringList()
|
||||
<< ItemPinnedLoader::tr("Ctrl+Shift+P", "Shortcut to pin and unpin items");
|
||||
return c;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemPinned::ItemPinned(ItemWidget *childItem)
|
||||
: QWidget( childItem->widget()->parentWidget() )
|
||||
, ItemWidget(this)
|
||||
, m_border(new QWidget(this))
|
||||
, m_childItem(childItem)
|
||||
{
|
||||
m_childItem->widget()->setObjectName("item_child");
|
||||
m_childItem->widget()->setParent(this);
|
||||
|
||||
m_border->setFixedWidth( pointsToPixels(6) );
|
||||
|
||||
// Set pinned item border color.
|
||||
const auto *parent = parentWidget();
|
||||
auto color = parent->palette().color(QPalette::Background);
|
||||
const int lightThreshold = 100;
|
||||
const bool menuBackgrounIsLight = color.lightness() > lightThreshold;
|
||||
color.setHsl(
|
||||
color.hue(),
|
||||
color.saturation(),
|
||||
qMax(0, qMin(255, color.lightness() + (menuBackgrounIsLight ? -200 : 50)))
|
||||
);
|
||||
const auto styleSheet = QString("background-color: rgba(%1,%2,%3,15\\%)")
|
||||
.arg(color.red())
|
||||
.arg(color.green())
|
||||
.arg(color.blue());
|
||||
m_border->setStyleSheet(styleSheet);
|
||||
|
||||
QBoxLayout *layout;
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing( pointsToPixels(5) );
|
||||
|
||||
layout->addWidget(m_childItem->widget());
|
||||
layout->addStretch();
|
||||
layout->addWidget(m_border);
|
||||
}
|
||||
|
||||
void ItemPinned::highlight(const QRegExp &re, const QFont &highlightFont, const QPalette &highlightPalette)
|
||||
{
|
||||
m_childItem->setHighlight(re, highlightFont, highlightPalette);
|
||||
}
|
||||
|
||||
QWidget *ItemPinned::createEditor(QWidget *parent) const
|
||||
{
|
||||
return m_childItem->createEditor(parent);
|
||||
}
|
||||
|
||||
void ItemPinned::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
return m_childItem->setEditorData(editor, index);
|
||||
}
|
||||
|
||||
void ItemPinned::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
|
||||
{
|
||||
return m_childItem->setModelData(editor, model, index);
|
||||
}
|
||||
|
||||
bool ItemPinned::hasChanges(QWidget *editor) const
|
||||
{
|
||||
return m_childItem->hasChanges(editor);
|
||||
}
|
||||
|
||||
QObject *ItemPinned::createExternalEditor(const QModelIndex &index, QWidget *parent) const
|
||||
{
|
||||
return m_childItem->createExternalEditor(index, parent);
|
||||
}
|
||||
|
||||
void ItemPinned::updateSize(const QSize &maximumSize, int idealWidth)
|
||||
{
|
||||
setMinimumWidth(idealWidth);
|
||||
setMaximumWidth(maximumSize.width());
|
||||
const int width = m_border->width() + layout()->spacing();
|
||||
const int childItemWidth = idealWidth - width;
|
||||
const auto childItemMaximumSize = QSize(maximumSize.width() - width, maximumSize.height());
|
||||
m_childItem->updateSize(childItemMaximumSize, childItemWidth);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
bool ItemPinnedScriptable::isPinned()
|
||||
{
|
||||
const auto args = currentArguments();
|
||||
for (const auto &arg : args) {
|
||||
bool ok;
|
||||
const int row = arg.toInt(&ok);
|
||||
if (ok) {
|
||||
const auto result = call("read", QVariantList() << "?" << row);
|
||||
if ( result.toByteArray().contains(mimePinned) )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ItemPinnedScriptable::pin()
|
||||
{
|
||||
const auto args = currentArguments();
|
||||
for (const auto &arg : args) {
|
||||
bool ok;
|
||||
const int row = arg.toInt(&ok);
|
||||
if (ok)
|
||||
call("change", QVariantList() << row << mimePinned << QString());
|
||||
}
|
||||
}
|
||||
|
||||
void ItemPinnedScriptable::unpin()
|
||||
{
|
||||
const auto args = currentArguments();
|
||||
for (const auto &arg : args) {
|
||||
bool ok;
|
||||
const int row = arg.toInt(&ok);
|
||||
if (ok)
|
||||
call("change", QVariantList() << row << mimePinned << QVariant());
|
||||
}
|
||||
}
|
||||
|
||||
void ItemPinnedScriptable::pinData()
|
||||
{
|
||||
call("setData", QVariantList() << mimePinned << QString());
|
||||
}
|
||||
|
||||
void ItemPinnedScriptable::unpinData()
|
||||
{
|
||||
call("removeData", QVariantList() << mimePinned);
|
||||
}
|
||||
|
||||
ItemPinnedSaver::ItemPinnedSaver(QAbstractItemModel *model, QVariantMap &settings, const ItemSaverPtr &saver)
|
||||
: m_model(model)
|
||||
, m_settings(settings)
|
||||
, m_saver(saver)
|
||||
{
|
||||
connect( model, SIGNAL(rowsInserted(QModelIndex,int,int)),
|
||||
SLOT(onRowsInserted(QModelIndex,int,int)) );
|
||||
connect( model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
|
||||
SLOT(onRowsRemoved(QModelIndex,int,int)) );
|
||||
connect( model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
|
||||
SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) );
|
||||
connect( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
|
||||
SLOT(onDataChanged(QModelIndex,QModelIndex)) );
|
||||
|
||||
updateLastPinned( 0, m_model->rowCount() );
|
||||
}
|
||||
|
||||
bool ItemPinnedSaver::saveItems(const QString &tabName, const QAbstractItemModel &model, QIODevice *file)
|
||||
{
|
||||
return m_saver->saveItems(tabName, model, file);
|
||||
}
|
||||
|
||||
bool ItemPinnedSaver::canRemoveItems(const QList<QModelIndex> &indexList, QString *error)
|
||||
{
|
||||
const bool containsPinnedItems = std::any_of(
|
||||
std::begin(indexList), std::end(indexList), isPinned);
|
||||
|
||||
if (!containsPinnedItems)
|
||||
return m_saver->canRemoveItems(indexList, error);
|
||||
|
||||
if (error) {
|
||||
*error = "Removing pinned item is not allowed (unpin item first)";
|
||||
return false;
|
||||
}
|
||||
|
||||
QMessageBox::information(
|
||||
QApplication::activeWindow(),
|
||||
ItemPinnedLoader::tr("Cannot Remove Pinned Items"),
|
||||
ItemPinnedLoader::tr("Unpin items first to remove them.") );
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ItemPinnedSaver::canMoveItems(const QList<QModelIndex> &indexList)
|
||||
{
|
||||
return m_saver->canMoveItems(indexList);
|
||||
}
|
||||
|
||||
void ItemPinnedSaver::itemsRemovedByUser(const QList<QModelIndex> &indexList)
|
||||
{
|
||||
m_saver->itemsRemovedByUser(indexList);
|
||||
}
|
||||
|
||||
QVariantMap ItemPinnedSaver::copyItem(const QAbstractItemModel &model, const QVariantMap &itemData)
|
||||
{
|
||||
return m_saver->copyItem(model, itemData);
|
||||
}
|
||||
|
||||
void ItemPinnedSaver::onRowsInserted(const QModelIndex &, int start, int end)
|
||||
{
|
||||
if (!m_model || m_lastPinned < start) {
|
||||
updateLastPinned(start, end);
|
||||
return;
|
||||
}
|
||||
|
||||
disconnect( m_model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
|
||||
this, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) );
|
||||
|
||||
// Shift rows below inserted up.
|
||||
const int rowCount = end - start + 1;
|
||||
for (int row = end + 1; row <= m_lastPinned + rowCount; ++row) {
|
||||
const auto index = m_model->index(row, 0);
|
||||
if ( isPinned(index) )
|
||||
moveRow(row, row - rowCount);
|
||||
}
|
||||
|
||||
connect( m_model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
|
||||
SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) );
|
||||
}
|
||||
|
||||
void ItemPinnedSaver::onRowsRemoved(const QModelIndex &, int start, int end)
|
||||
{
|
||||
if (!m_model || m_lastPinned < start)
|
||||
return;
|
||||
|
||||
disconnect( m_model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
|
||||
this, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) );
|
||||
|
||||
// Shift rows below removed down.
|
||||
const int rowCount = end - start + 1;
|
||||
for (int row = m_lastPinned - rowCount; row >= start; --row) {
|
||||
const auto index = m_model->index(row, 0);
|
||||
if ( isPinned(index) )
|
||||
moveRow(row, row + rowCount + 1);
|
||||
}
|
||||
|
||||
connect( m_model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
|
||||
SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) );
|
||||
}
|
||||
|
||||
void ItemPinnedSaver::onRowsMoved(const QModelIndex &, int start, int end, const QModelIndex &, int destinationRow)
|
||||
{
|
||||
if ( (m_lastPinned < start && m_lastPinned < destinationRow)
|
||||
|| (end < m_lastPinned && destinationRow < m_lastPinned) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (start < destinationRow)
|
||||
updateLastPinned(start, destinationRow + end - start + 1);
|
||||
else
|
||||
updateLastPinned(destinationRow, end);
|
||||
}
|
||||
|
||||
void ItemPinnedSaver::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
|
||||
{
|
||||
if ( bottomRight.row() < m_lastPinned )
|
||||
return;
|
||||
|
||||
updateLastPinned( topLeft.row(), bottomRight.row() );
|
||||
}
|
||||
|
||||
void ItemPinnedSaver::moveRow(int from, int to)
|
||||
{
|
||||
#if QT_VERSION < 0x050000
|
||||
QMetaObject::invokeMethod(m_model, "moveRow", Q_ARG(int, from), Q_ARG(int, to));
|
||||
#else
|
||||
m_model->moveRow(QModelIndex(), from, QModelIndex(), to);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ItemPinnedSaver::updateLastPinned(int from, int to)
|
||||
{
|
||||
for (int row = to; row >= from; --row) {
|
||||
const auto index = m_model->index(row, 0);
|
||||
if ( isPinned(index) ) {
|
||||
m_lastPinned = row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ItemPinnedLoader::ItemPinnedLoader()
|
||||
{
|
||||
}
|
||||
|
||||
ItemPinnedLoader::~ItemPinnedLoader() = default;
|
||||
|
||||
QStringList ItemPinnedLoader::formatsToSave() const
|
||||
{
|
||||
return QStringList() << mimePinned;
|
||||
}
|
||||
|
||||
QVariantMap ItemPinnedLoader::applySettings()
|
||||
{
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
QWidget *ItemPinnedLoader::createSettingsWidget(QWidget *parent)
|
||||
{
|
||||
ui.reset(new Ui::ItemPinnedSettings);
|
||||
QWidget *w = new QWidget(parent);
|
||||
ui->setupUi(w);
|
||||
|
||||
connect( ui->pushButtonAddCommands, SIGNAL(clicked()),
|
||||
this, SLOT(addCommands()) );
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
ItemWidget *ItemPinnedLoader::transform(ItemWidget *itemWidget, const QModelIndex &index)
|
||||
{
|
||||
return isPinned(index) ? new ItemPinned(itemWidget) : nullptr;
|
||||
}
|
||||
|
||||
ItemSaverPtr ItemPinnedLoader::transformSaver(const ItemSaverPtr &saver, QAbstractItemModel *model)
|
||||
{
|
||||
return std::make_shared<ItemPinnedSaver>(model, m_settings, saver);
|
||||
}
|
||||
|
||||
QObject *ItemPinnedLoader::tests(const TestInterfacePtr &test) const
|
||||
{
|
||||
#ifdef HAS_TESTS
|
||||
QObject *tests = new ItemPinnedTests(test);
|
||||
return tests;
|
||||
#else
|
||||
Q_UNUSED(test);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
ItemScriptable *ItemPinnedLoader::scriptableObject(QObject *parent)
|
||||
{
|
||||
return new ItemPinnedScriptable(parent);
|
||||
}
|
||||
|
||||
QList<Command> ItemPinnedLoader::commands() const
|
||||
{
|
||||
QList<Command> commands;
|
||||
|
||||
Command c;
|
||||
|
||||
c = dummyPinCommand();
|
||||
c.name = tr("Pin");
|
||||
c.input = "!OUTPUT";
|
||||
c.output = mimePinned;
|
||||
c.cmd = "copyq: plugins.itempinned.pinData()";
|
||||
commands.append(c);
|
||||
|
||||
c = dummyPinCommand();
|
||||
c.name = tr("Unpin");
|
||||
c.input = mimePinned;
|
||||
c.cmd = "copyq: plugins.itempinned.unpinData()";
|
||||
commands.append(c);
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
void ItemPinnedLoader::addCommands()
|
||||
{
|
||||
emit addCommands(commands());
|
||||
}
|
||||
|
||||
Q_EXPORT_PLUGIN2(itempinned, ItemPinnedLoader)
|
@ -0,0 +1,164 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMPINNED_H
|
||||
#define ITEMPINNED_H
|
||||
|
||||
#include "gui/icons.h"
|
||||
#include "item/itemwidget.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Ui {
|
||||
class ItemPinnedSettings;
|
||||
}
|
||||
|
||||
class ItemPinned : public QWidget, public ItemWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ItemPinned(ItemWidget *childItem);
|
||||
|
||||
protected:
|
||||
void highlight(const QRegExp &re, const QFont &highlightFont,
|
||||
const QPalette &highlightPalette) override;
|
||||
|
||||
QWidget *createEditor(QWidget *parent) const override;
|
||||
|
||||
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
|
||||
void setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
bool hasChanges(QWidget *editor) const override;
|
||||
|
||||
QObject *createExternalEditor(const QModelIndex &index, QWidget *parent) const override;
|
||||
|
||||
void updateSize(const QSize &maximumSize, int idealWidth) override;
|
||||
|
||||
private:
|
||||
QWidget *m_border;
|
||||
std::unique_ptr<ItemWidget> m_childItem;
|
||||
};
|
||||
|
||||
class ItemPinnedScriptable : public ItemScriptable
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ItemPinnedScriptable(QObject *parent) : ItemScriptable(parent) {}
|
||||
|
||||
public slots:
|
||||
bool isPinned();
|
||||
|
||||
void pin();
|
||||
void unpin();
|
||||
|
||||
void pinData();
|
||||
void unpinData();
|
||||
};
|
||||
|
||||
class ItemPinnedSaver : public QObject, public ItemSaverInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ItemPinnedSaver(QAbstractItemModel *model, QVariantMap &settings, const ItemSaverPtr &saver);
|
||||
|
||||
bool saveItems(const QString &tabName, const QAbstractItemModel &model, QIODevice *file) override;
|
||||
|
||||
bool canRemoveItems(const QList<QModelIndex> &indexList, QString *error) override;
|
||||
|
||||
bool canMoveItems(const QList<QModelIndex> &indexList) override;
|
||||
|
||||
void itemsRemovedByUser(const QList<QModelIndex> &indexList) override;
|
||||
|
||||
QVariantMap copyItem(const QAbstractItemModel &model, const QVariantMap &itemData) override;
|
||||
|
||||
private slots:
|
||||
void onRowsInserted(const QModelIndex &parent, int start, int end);
|
||||
void onRowsRemoved(const QModelIndex &parent, int start, int end);
|
||||
void onRowsMoved(const QModelIndex &, int start, int end, const QModelIndex &, int destinationRow);
|
||||
void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
|
||||
|
||||
private:
|
||||
void moveRow(int from, int to);
|
||||
void updateLastPinned(int from, int to);
|
||||
|
||||
QPointer<QAbstractItemModel> m_model;
|
||||
QVariantMap m_settings;
|
||||
ItemSaverPtr m_saver;
|
||||
|
||||
// Last pinned row in list (improves performace of updates).
|
||||
int m_lastPinned = -1;
|
||||
};
|
||||
|
||||
class ItemPinnedLoader : public QObject, public ItemLoaderInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID COPYQ_PLUGIN_ITEM_LOADER_ID)
|
||||
Q_INTERFACES(ItemLoaderInterface)
|
||||
|
||||
public:
|
||||
ItemPinnedLoader();
|
||||
~ItemPinnedLoader();
|
||||
|
||||
QString id() const override { return "itempinned"; }
|
||||
QString name() const override { return tr("Pinned Items"); }
|
||||
QString author() const override { return QString(); }
|
||||
QString description() const override {
|
||||
return tr("Pin items to lock them in current row and avoid deletion (unless unpinned).");
|
||||
}
|
||||
QVariant icon() const override { return QVariant(IconThumbTack); }
|
||||
|
||||
QStringList formatsToSave() const override;
|
||||
|
||||
QVariantMap applySettings() override;
|
||||
|
||||
void loadSettings(const QVariantMap &settings) override { m_settings = settings; }
|
||||
|
||||
QWidget *createSettingsWidget(QWidget *parent) override;
|
||||
|
||||
ItemWidget *transform(ItemWidget *itemWidget, const QModelIndex &index) override;
|
||||
|
||||
ItemSaverPtr transformSaver(const ItemSaverPtr &saver, QAbstractItemModel *model) override;
|
||||
|
||||
QObject *tests(const TestInterfacePtr &test) const override;
|
||||
|
||||
const QObject *signaler() const override { return this; }
|
||||
|
||||
ItemScriptable *scriptableObject(QObject *parent) override;
|
||||
|
||||
QList<Command> commands() const override;
|
||||
|
||||
signals:
|
||||
void addCommands(const QList<Command> &commands);
|
||||
|
||||
private slots:
|
||||
void addCommands();
|
||||
|
||||
private:
|
||||
QVariantMap m_settings;
|
||||
std::unique_ptr<Ui::ItemPinnedSettings> ui;
|
||||
ItemLoaderPtr m_transformedLoader;
|
||||
};
|
||||
|
||||
#endif // ITEMPINNED_H
|
@ -0,0 +1,13 @@
|
||||
include(../plugins_common.pri)
|
||||
|
||||
HEADERS += itempinned.h
|
||||
SOURCES += itempinned.cpp \
|
||||
../../src/common/display.cpp
|
||||
FORMS += itempinnedsettings.ui
|
||||
TARGET = $$qtLibraryTarget(itempinned)
|
||||
|
||||
CONFIG(debug, debug|release) {
|
||||
SOURCES += tests/itempinnedtests.cpp
|
||||
HEADERS += tests/itempinnedtests.h
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ItemPinnedSettings</class>
|
||||
<widget class="QWidget" name="ItemPinnedSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>358</width>
|
||||
<height>141</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonAddCommands">
|
||||
<property name="text">
|
||||
<string>Add Actions to Menu and Toolbar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,164 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itempinnedtests.h"
|
||||
|
||||
#include "tests/test_utils.h"
|
||||
|
||||
ItemPinnedTests::ItemPinnedTests(const TestInterfacePtr &test, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_test(test)
|
||||
{
|
||||
}
|
||||
|
||||
void ItemPinnedTests::initTestCase()
|
||||
{
|
||||
TEST(m_test->initTestCase());
|
||||
}
|
||||
|
||||
void ItemPinnedTests::cleanupTestCase()
|
||||
{
|
||||
TEST(m_test->cleanupTestCase());
|
||||
}
|
||||
|
||||
void ItemPinnedTests::init()
|
||||
{
|
||||
TEST(m_test->init());
|
||||
}
|
||||
|
||||
void ItemPinnedTests::cleanup()
|
||||
{
|
||||
TEST( m_test->cleanup() );
|
||||
}
|
||||
|
||||
void ItemPinnedTests::isPinned()
|
||||
{
|
||||
RUN("add" << "b" << "a", "");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(1)", "false\n");
|
||||
}
|
||||
|
||||
void ItemPinnedTests::pin()
|
||||
{
|
||||
RUN("add" << "b" << "a", "");
|
||||
RUN("-e" << "plugins.itempinned.pin(1)", "");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");
|
||||
|
||||
RUN("-e" << "plugins.itempinned.pin(0)", "");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(0)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
|
||||
}
|
||||
|
||||
void ItemPinnedTests::pinMultiple()
|
||||
{
|
||||
RUN("add" << "d" << "c" << "b" << "a", "");
|
||||
RUN("-e" << "plugins.itempinned.pin(1, 2)", "");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(2)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(3)", "false\n");
|
||||
|
||||
RUN("-e" << "plugins.itempinned.pin(1, 2 ,3)", "");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(2)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(3)", "true\n");
|
||||
}
|
||||
|
||||
void ItemPinnedTests::unpin()
|
||||
{
|
||||
RUN("add" << "b" << "a", "");
|
||||
RUN("-e" << "plugins.itempinned.pin(0, 1)", "");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(0)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
|
||||
|
||||
RUN("-e" << "plugins.itempinned.unpin(0)", "");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
|
||||
|
||||
RUN("-e" << "plugins.itempinned.unpin(1)", "");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(0)", "false\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(1)", "false\n");
|
||||
}
|
||||
|
||||
void ItemPinnedTests::unpinMultiple()
|
||||
{
|
||||
RUN("add" << "d" << "c" << "b" << "a", "");
|
||||
RUN("-e" << "plugins.itempinned.pin(0, 1, 2, 3)", "");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(0)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(1)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(2)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(3)", "true\n");
|
||||
|
||||
RUN("-e" << "plugins.itempinned.unpin(1, 2)", "");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(0)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(1)", "false\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(2)", "false\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(3)", "true\n");
|
||||
|
||||
RUN("-e" << "plugins.itempinned.unpin(2, 3)", "");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(0)", "true\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(1)", "false\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(2)", "false\n");
|
||||
RUN("-e" << "plugins.itempinned.isPinned(3)", "false\n");
|
||||
}
|
||||
|
||||
void ItemPinnedTests::removePinnedThrows()
|
||||
{
|
||||
RUN("add" << "b" << "a", "");
|
||||
RUN("-e" << "plugins.itempinned.pin(0, 1)", "");
|
||||
|
||||
RUN_EXPECT_ERROR("remove" << "0" << "1", CommandException);
|
||||
RUN("separator" << " " << "read" << "0" << "1", "a b");
|
||||
}
|
||||
|
||||
void ItemPinnedTests::pinToRow()
|
||||
{
|
||||
const auto read = Args() << "separator" << " " << "read";
|
||||
|
||||
RUN("add" << "a", "");
|
||||
RUN("-e" << "plugins.itempinned.pin(0)", "");
|
||||
RUN("add" << "b", "");
|
||||
RUN(read << "0" << "1", "a b");
|
||||
|
||||
RUN("add" << "c", "");
|
||||
RUN(read << "0" << "1" << "2", "a c b");
|
||||
|
||||
RUN("-e" << "plugins.itempinned.pin(1)", "");
|
||||
RUN("add" << "d", "");
|
||||
RUN(read << "0" << "1" << "2" << "3", "a c d b");
|
||||
|
||||
RUN("-e" << "plugins.itempinned.pin(2)", "");
|
||||
RUN("-e" << "plugins.itempinned.unpin(1); remove(1)", "");
|
||||
RUN(read << "0" << "1" << "2", "a b d");
|
||||
}
|
||||
|
||||
void ItemPinnedTests::fullTab()
|
||||
{
|
||||
RUN("config" << "maxitems" << "3", "3\n");
|
||||
RUN("add" << "c" << "b" << "a", "");
|
||||
RUN("-e" << "plugins.itempinned.pin(0,1,2)", "");
|
||||
|
||||
// Tab is full and no items can be removed.
|
||||
RUN_EXPECT_ERROR("add" << "X", CommandException);
|
||||
RUN_EXPECT_ERROR("write" << "1" << "text/plain" << "X", CommandException);
|
||||
RUN("separator" << " " << "read" << "0" << "1" << "2", "a b c");
|
||||
RUN("size", "3\n");
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMPINNEDTESTS_H
|
||||
#define ITEMPINNEDTESTS_H
|
||||
|
||||
#include "tests/testinterface.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class ItemPinnedTests : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ItemPinnedTests(const TestInterfacePtr &test, QObject *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
void isPinned();
|
||||
void pin();
|
||||
void pinMultiple();
|
||||
void unpin();
|
||||
void unpinMultiple();
|
||||
|
||||
void removePinnedThrows();
|
||||
|
||||
void pinToRow();
|
||||
|
||||
void fullTab();
|
||||
|
||||
private:
|
||||
TestInterfacePtr m_test;
|
||||
};
|
||||
|
||||
#endif // ITEMPINNEDTESTS_H
|
@ -0,0 +1,13 @@
|
||||
set(copyq_plugin_itemsync_SOURCES
|
||||
../../src/common/config.cpp
|
||||
../../src/common/log.cpp
|
||||
../../src/common/mimetypes.cpp
|
||||
../../src/gui/iconfont.cpp
|
||||
../../src/gui/iconselectbutton.cpp
|
||||
../../src/gui/iconselectdialog.cpp
|
||||
../../src/gui/iconwidget.cpp
|
||||
../../src/item/serialize.cpp
|
||||
)
|
||||
|
||||
copyq_add_plugin(itemsync)
|
||||
|
@ -0,0 +1,800 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "filewatcher.h"
|
||||
|
||||
#include "common/contenttype.h"
|
||||
#include "common/log.h"
|
||||
#include "item/serialize.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDir>
|
||||
#include <QMimeData>
|
||||
#include <QUrl>
|
||||
|
||||
const char mimeExtensionMap[] = COPYQ_MIME_PREFIX_ITEMSYNC "mime-to-extension-map";
|
||||
const char mimeBaseName[] = COPYQ_MIME_PREFIX_ITEMSYNC "basename";
|
||||
const char mimeNoSave[] = COPYQ_MIME_PREFIX_ITEMSYNC "no-save";
|
||||
const char mimeSyncPath[] = COPYQ_MIME_PREFIX_ITEMSYNC "sync-path";
|
||||
const char mimeNoFormat[] = COPYQ_MIME_PREFIX_ITEMSYNC "no-format";
|
||||
const char mimeUnknownFormats[] = COPYQ_MIME_PREFIX_ITEMSYNC "unknown-formats";
|
||||
|
||||
struct Ext {
|
||||
Ext() : extension(), format() {}
|
||||
|
||||
Ext(const QString &extension, const QString &format)
|
||||
: extension(extension)
|
||||
, format(format)
|
||||
{}
|
||||
|
||||
QString extension;
|
||||
QString format;
|
||||
};
|
||||
|
||||
struct BaseNameExtensions {
|
||||
explicit BaseNameExtensions(const QString &baseName = QString(),
|
||||
const QList<Ext> &exts = QList<Ext>())
|
||||
: baseName(baseName)
|
||||
, exts(exts) {}
|
||||
QString baseName;
|
||||
QList<Ext> exts;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
const char dataFileSuffix[] = "_copyq.dat";
|
||||
const char noteFileSuffix[] = "_note.txt";
|
||||
|
||||
const int updateItemsIntervalMs = 5000; // Interval to update items after a file has changed.
|
||||
|
||||
const qint64 sizeLimit = 10 << 20;
|
||||
|
||||
FileFormat getFormatSettingsFromFileName(const QString &fileName,
|
||||
const QList<FileFormat> &formatSettings,
|
||||
QString *foundExt = nullptr)
|
||||
{
|
||||
for (const auto &format : formatSettings) {
|
||||
for ( const auto &ext : format.extensions ) {
|
||||
if ( fileName.endsWith(ext) ) {
|
||||
if (foundExt)
|
||||
*foundExt = ext;
|
||||
return format;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FileFormat();
|
||||
}
|
||||
|
||||
void getBaseNameAndExtension(const QString &fileName, QString *baseName, QString *ext,
|
||||
const QList<FileFormat> &formatSettings)
|
||||
{
|
||||
ext->clear();
|
||||
|
||||
const FileFormat fileFormat = getFormatSettingsFromFileName(fileName, formatSettings, ext);
|
||||
|
||||
if ( !fileFormat.isValid() ) {
|
||||
const int i = fileName.lastIndexOf('.');
|
||||
if (i != -1)
|
||||
*ext = fileName.mid(i);
|
||||
}
|
||||
|
||||
*baseName = fileName.left( fileName.size() - ext->size() );
|
||||
|
||||
if ( baseName->endsWith('.') ) {
|
||||
baseName->chop(1);
|
||||
ext->prepend('.');
|
||||
}
|
||||
}
|
||||
|
||||
QList<Ext> fileExtensionsAndFormats()
|
||||
{
|
||||
static QList<Ext> exts;
|
||||
|
||||
if ( exts.isEmpty() ) {
|
||||
exts.append( Ext(noteFileSuffix, mimeItemNotes) );
|
||||
|
||||
exts.append( Ext(".bmp", "image/bmp") );
|
||||
exts.append( Ext(".gif", "image/gif") );
|
||||
exts.append( Ext(".html", mimeHtml) );
|
||||
exts.append( Ext("_inkscape.svg", "image/x-inkscape-svg-compressed") );
|
||||
exts.append( Ext(".jpg", "image/jpeg") );
|
||||
exts.append( Ext(".jpg", "image/jpeg") );
|
||||
exts.append( Ext(".png", "image/png") );
|
||||
exts.append( Ext(".txt", mimeText) );
|
||||
exts.append( Ext(".uri", mimeUriList) );
|
||||
exts.append( Ext(".xml", "application/xml") );
|
||||
exts.append( Ext(".svg", "image/svg+xml") );
|
||||
exts.append( Ext(".xml", "text/xml") );
|
||||
}
|
||||
|
||||
return exts;
|
||||
}
|
||||
|
||||
QString findByFormat(const QString &format, const QList<FileFormat> &formatSettings)
|
||||
{
|
||||
// Find in default extensions.
|
||||
const QList<Ext> &exts = fileExtensionsAndFormats();
|
||||
|
||||
for (const auto &ext : exts) {
|
||||
if (ext.format == format)
|
||||
return ext.extension;
|
||||
}
|
||||
|
||||
// Find in user defined extensions.
|
||||
for (const auto &fileFormat : formatSettings) {
|
||||
if ( !fileFormat.extensions.isEmpty() && fileFormat.itemMime != "-"
|
||||
&& format == fileFormat.itemMime )
|
||||
{
|
||||
return fileFormat.extensions.first();
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
Ext findByExtension(const QString &fileName, const QList<FileFormat> &formatSettings)
|
||||
{
|
||||
// Is internal data format?
|
||||
if ( fileName.endsWith(dataFileSuffix) )
|
||||
return Ext(dataFileSuffix, mimeUnknownFormats);
|
||||
|
||||
// Find in user defined formats.
|
||||
bool hasUserFormat = false;
|
||||
for (const auto &format : formatSettings) {
|
||||
for (const auto &ext : format.extensions) {
|
||||
if ( fileName.endsWith(ext) ) {
|
||||
if ( format.itemMime.isEmpty() )
|
||||
hasUserFormat = true;
|
||||
else
|
||||
return Ext(ext, format.itemMime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find in default formats.
|
||||
const QList<Ext> &exts = fileExtensionsAndFormats();
|
||||
|
||||
for (const auto &ext : exts) {
|
||||
if ( fileName.endsWith(ext.extension) )
|
||||
return ext;
|
||||
}
|
||||
|
||||
return hasUserFormat ? Ext(QString(), mimeNoFormat) : Ext();
|
||||
}
|
||||
|
||||
bool saveItemFile(const QString &filePath, const QByteArray &bytes,
|
||||
QStringList *existingFiles, bool hashChanged = true)
|
||||
{
|
||||
if ( !existingFiles->removeOne(filePath) || hashChanged ) {
|
||||
QFile f(filePath);
|
||||
if ( !f.open(QIODevice::WriteOnly) || f.write(bytes) == -1 ) {
|
||||
log( QString("ItemSync: %1").arg(f.errorString()), LogError );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canUseFile(QFileInfo &info)
|
||||
{
|
||||
return !info.isHidden() && !info.fileName().startsWith('.') && info.isReadable();
|
||||
}
|
||||
|
||||
bool getBaseNameExtension(const QString &filePath, const QList<FileFormat> &formatSettings,
|
||||
QString *baseName, Ext *ext)
|
||||
{
|
||||
QFileInfo info(filePath);
|
||||
if ( !canUseFile(info) )
|
||||
return false;
|
||||
|
||||
*ext = findByExtension(filePath, formatSettings);
|
||||
if ( ext->format.isEmpty() || ext->format == "-" )
|
||||
return false;
|
||||
|
||||
const QString fileName = info.fileName();
|
||||
*baseName = fileName.left( fileName.size() - ext->extension.size() );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BaseNameExtensionsList listFiles(const QStringList &files,
|
||||
const QList<FileFormat> &formatSettings)
|
||||
{
|
||||
BaseNameExtensionsList fileList;
|
||||
QMap<QString, int> fileMap;
|
||||
|
||||
for (const auto &filePath : files) {
|
||||
QString baseName;
|
||||
Ext ext;
|
||||
if ( getBaseNameExtension(filePath, formatSettings, &baseName, &ext) ) {
|
||||
int i = fileMap.value(baseName, -1);
|
||||
if (i == -1) {
|
||||
i = fileList.size();
|
||||
fileList.append( BaseNameExtensions(baseName) );
|
||||
fileMap.insert(baseName, i);
|
||||
}
|
||||
|
||||
fileList[i].exts.append(ext);
|
||||
}
|
||||
}
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
/// Load hash of all existing files to map (hash -> filename).
|
||||
QStringList listFiles(const QDir &dir, const QDir::SortFlags &sortFlags = QDir::NoSort)
|
||||
{
|
||||
QStringList files;
|
||||
|
||||
const QDir::Filters itemFileFilter = QDir::Files | QDir::Readable | QDir::Writable;
|
||||
for ( const auto &fileName : dir.entryList(itemFileFilter, sortFlags) ) {
|
||||
const QString path = dir.absoluteFilePath(fileName);
|
||||
QFileInfo info(path);
|
||||
if ( canUseFile(info) )
|
||||
files.append(path);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/// Return true only if no file name in @a fileNames starts with @a baseName.
|
||||
bool isUniqueBaseName(const QString &baseName, const QStringList &fileNames,
|
||||
const QStringList &baseNames = QStringList())
|
||||
{
|
||||
if ( baseNames.contains(baseName) )
|
||||
return false;
|
||||
|
||||
for (const auto &fileName : fileNames) {
|
||||
if ( fileName.startsWith(baseName) )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void moveFormatFiles(const QString &oldPath, const QString &newPath,
|
||||
const QVariantMap &mimeToExtension)
|
||||
{
|
||||
for ( const auto &extValue : mimeToExtension.values() ) {
|
||||
const QString ext = extValue.toString();
|
||||
QFile::rename(oldPath + ext, newPath + ext);
|
||||
}
|
||||
}
|
||||
|
||||
void copyFormatFiles(const QString &oldPath, const QString &newPath,
|
||||
const QVariantMap &mimeToExtension)
|
||||
{
|
||||
for ( const auto &extValue : mimeToExtension.values() ) {
|
||||
const QString ext = extValue.toString();
|
||||
QFile::copy(oldPath + ext, newPath + ext);
|
||||
}
|
||||
}
|
||||
|
||||
void removeFormatFiles(const QString &path, const QVariantMap &mimeToExtension)
|
||||
{
|
||||
for ( const auto &extValue : mimeToExtension.values() )
|
||||
QFile::remove(path + extValue.toString());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString FileWatcher::getBaseName(const QModelIndex &index)
|
||||
{
|
||||
return index.data(contentType::data).toMap().value(mimeBaseName).toString();
|
||||
}
|
||||
|
||||
bool FileWatcher::isOwnBaseName(const QString &baseName)
|
||||
{
|
||||
static const QRegExp re("copyq_\\d*");
|
||||
return re.exactMatch(baseName);
|
||||
}
|
||||
|
||||
void FileWatcher::removeFilesForRemovedIndex(const QString &tabPath, const QModelIndex &index)
|
||||
{
|
||||
const QAbstractItemModel *model = index.model();
|
||||
if (!model)
|
||||
return;
|
||||
|
||||
const QString baseName = FileWatcher::getBaseName(index);
|
||||
if ( baseName.isEmpty() )
|
||||
return;
|
||||
|
||||
// Check if item is still present in list (drag'n'drop).
|
||||
bool remove = true;
|
||||
for (int i = 0; i < model->rowCount(); ++i) {
|
||||
const QModelIndex index2 = model->index(i, 0);
|
||||
if ( index2 != index && baseName == FileWatcher::getBaseName(index2) ) {
|
||||
remove = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!remove)
|
||||
return;
|
||||
|
||||
const QVariantMap itemData = index.data(contentType::data).toMap();
|
||||
const QVariantMap mimeToExtension = itemData.value(mimeExtensionMap).toMap();
|
||||
if ( mimeToExtension.isEmpty() )
|
||||
QFile::remove(tabPath + '/' + baseName);
|
||||
else
|
||||
removeFormatFiles(tabPath + '/' + baseName, mimeToExtension);
|
||||
}
|
||||
|
||||
Hash FileWatcher::calculateHash(const QByteArray &bytes)
|
||||
{
|
||||
return QCryptographicHash::hash(bytes, QCryptographicHash::Sha1);
|
||||
}
|
||||
|
||||
FileWatcher::FileWatcher(
|
||||
const QString &path,
|
||||
const QStringList &paths,
|
||||
QAbstractItemModel *model,
|
||||
int maxItems,
|
||||
const QList<FileFormat> &formatSettings,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_model(model)
|
||||
, m_formatSettings(formatSettings)
|
||||
, m_path(path)
|
||||
, m_valid(true)
|
||||
, m_indexData()
|
||||
, m_maxItems(maxItems)
|
||||
{
|
||||
#ifdef HAS_TESTS
|
||||
// Use smaller update interval for tests.
|
||||
if ( qgetenv("COPYQ_TEST_ID").isEmpty() )
|
||||
m_updateTimer.setInterval(updateItemsIntervalMs);
|
||||
else
|
||||
m_updateTimer.setInterval(100);
|
||||
#else
|
||||
m_updateTimer.setInterval(updateItemsIntervalMs);
|
||||
#endif
|
||||
m_updateTimer.setSingleShot(true);
|
||||
connect( &m_updateTimer, SIGNAL(timeout()),
|
||||
SLOT(updateItems()) );
|
||||
|
||||
connect( m_model.data(), SIGNAL(rowsInserted(QModelIndex, int, int)),
|
||||
this, SLOT(onRowsInserted(QModelIndex, int, int)), Qt::UniqueConnection );
|
||||
connect( m_model.data(), SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)),
|
||||
this, SLOT(onRowsRemoved(QModelIndex, int, int)), Qt::UniqueConnection );
|
||||
connect( m_model.data(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
|
||||
SLOT(onDataChanged(QModelIndex,QModelIndex)), Qt::UniqueConnection );
|
||||
|
||||
if (model->rowCount() > 0)
|
||||
saveItems(0, model->rowCount() - 1);
|
||||
|
||||
createItemsFromFiles( QDir(path), listFiles(paths, m_formatSettings) );
|
||||
|
||||
updateItems();
|
||||
}
|
||||
|
||||
bool FileWatcher::lock()
|
||||
{
|
||||
if ( !m_valid )
|
||||
return false;
|
||||
|
||||
m_valid = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileWatcher::unlock()
|
||||
{
|
||||
m_valid = true;
|
||||
}
|
||||
|
||||
bool FileWatcher::createItemFromFiles(const QDir &dir, const BaseNameExtensions &baseNameWithExts, int targetRow)
|
||||
{
|
||||
QVariantMap dataMap;
|
||||
QVariantMap mimeToExtension;
|
||||
|
||||
updateDataAndWatchFile(dir, baseNameWithExts, &dataMap, &mimeToExtension);
|
||||
|
||||
if ( !mimeToExtension.isEmpty() ) {
|
||||
dataMap.insert( mimeBaseName, QFileInfo(baseNameWithExts.baseName).fileName() );
|
||||
dataMap.insert(mimeExtensionMap, mimeToExtension);
|
||||
|
||||
if ( !createItem(dataMap, targetRow) )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileWatcher::createItemsFromFiles(const QDir &dir, const BaseNameExtensionsList &fileList)
|
||||
{
|
||||
for (const auto &baseNameWithExts : fileList) {
|
||||
if ( !createItemFromFiles(dir, baseNameWithExts, 0)
|
||||
|| m_model->rowCount() >= m_maxItems )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileWatcher::updateItems()
|
||||
{
|
||||
m_updateTimer.stop();
|
||||
|
||||
if ( !lock() )
|
||||
return;
|
||||
|
||||
QDir dir(m_path);
|
||||
const QStringList files = listFiles(dir, QDir::Time | QDir::Reversed);
|
||||
BaseNameExtensionsList fileList = listFiles(files, m_formatSettings);
|
||||
|
||||
for ( int row = 0; row < m_model->rowCount(); ++row ) {
|
||||
const QModelIndex index = m_model->index(row, 0);
|
||||
const QString baseName = getBaseName(index);
|
||||
|
||||
int i = 0;
|
||||
for ( i = 0; i < fileList.size() && fileList[i].baseName != baseName; ++i ) {}
|
||||
|
||||
QVariantMap dataMap;
|
||||
QVariantMap mimeToExtension;
|
||||
|
||||
if ( i < fileList.size() ) {
|
||||
updateDataAndWatchFile(dir, fileList[i], &dataMap, &mimeToExtension);
|
||||
fileList.removeAt(i);
|
||||
}
|
||||
|
||||
if ( mimeToExtension.isEmpty() ) {
|
||||
m_model->removeRow(row--);
|
||||
} else {
|
||||
dataMap.insert(mimeBaseName, baseName);
|
||||
dataMap.insert(mimeExtensionMap, mimeToExtension);
|
||||
updateIndexData(index, dataMap);
|
||||
}
|
||||
}
|
||||
|
||||
createItemsFromFiles(dir, fileList);
|
||||
|
||||
unlock();
|
||||
|
||||
m_updateTimer.start();
|
||||
}
|
||||
|
||||
void FileWatcher::onRowsInserted(const QModelIndex &, int first, int last)
|
||||
{
|
||||
saveItems(first, last);
|
||||
}
|
||||
|
||||
void FileWatcher::onDataChanged(const QModelIndex &a, const QModelIndex &b)
|
||||
{
|
||||
saveItems(a.row(), b.row());
|
||||
}
|
||||
|
||||
void FileWatcher::onRowsRemoved(const QModelIndex &, int first, int last)
|
||||
{
|
||||
for ( const auto &index : indexList(first, last) ) {
|
||||
Q_ASSERT(index.isValid());
|
||||
IndexDataList::iterator it = findIndexData(index);
|
||||
Q_ASSERT( it != m_indexData.end() );
|
||||
if ( isOwnBaseName(it->baseName) )
|
||||
removeFilesForRemovedIndex(m_path, index);
|
||||
m_indexData.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
FileWatcher::IndexDataList::iterator FileWatcher::findIndexData(const QModelIndex &index)
|
||||
{
|
||||
return qFind(m_indexData.begin(), m_indexData.end(), index);
|
||||
}
|
||||
|
||||
FileWatcher::IndexData &FileWatcher::indexData(const QModelIndex &index)
|
||||
{
|
||||
IndexDataList::iterator it = findIndexData(index);
|
||||
if ( it == m_indexData.end() )
|
||||
return *m_indexData.insert( m_indexData.end(), IndexData(index) );
|
||||
return *it;
|
||||
}
|
||||
|
||||
bool FileWatcher::createItem(const QVariantMap &dataMap, int targetRow)
|
||||
{
|
||||
const int row = qMax( 0, qMin(targetRow, m_model->rowCount()) );
|
||||
if ( m_model->insertRow(row) ) {
|
||||
const QModelIndex &index = m_model->index(row, 0);
|
||||
updateIndexData(index, dataMap);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileWatcher::updateIndexData(const QModelIndex &index, const QVariantMap &itemData)
|
||||
{
|
||||
m_model->setData(index, itemData, contentType::data);
|
||||
|
||||
// Item base name is non-empty.
|
||||
const QString baseName = getBaseName(index);
|
||||
Q_ASSERT( !baseName.isEmpty() );
|
||||
|
||||
const QVariantMap mimeToExtension = itemData.value(mimeExtensionMap).toMap();
|
||||
|
||||
IndexData &data = indexData(index);
|
||||
|
||||
data.baseName = baseName;
|
||||
|
||||
QMap<QString, Hash> &formatData = data.formatHash;
|
||||
formatData.clear();
|
||||
|
||||
for ( const auto &format : mimeToExtension.keys() ) {
|
||||
if ( !format.startsWith(COPYQ_MIME_PREFIX_ITEMSYNC) )
|
||||
formatData.insert(format, calculateHash(itemData.value(format).toByteArray()) );
|
||||
}
|
||||
}
|
||||
|
||||
QList<QModelIndex> FileWatcher::indexList(int first, int last)
|
||||
{
|
||||
QList<QModelIndex> indexList;
|
||||
for (int i = first; i <= last; ++i)
|
||||
indexList.append( m_model->index(i, 0) );
|
||||
return indexList;
|
||||
}
|
||||
|
||||
void FileWatcher::saveItems(int first, int last)
|
||||
{
|
||||
if ( !lock() )
|
||||
return;
|
||||
|
||||
const QList<QModelIndex> indexList = this->indexList(first, last);
|
||||
|
||||
// Create path if doesn't exist.
|
||||
QDir dir(m_path);
|
||||
if ( !dir.mkpath(".") ) {
|
||||
log( tr("Failed to create synchronization directory \"%1\"!").arg(m_path) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !renameMoveCopy(dir, indexList) )
|
||||
return;
|
||||
|
||||
QStringList existingFiles = listFiles(dir);
|
||||
|
||||
for (const auto &index : indexList) {
|
||||
if ( !index.isValid() )
|
||||
continue;
|
||||
|
||||
const QString baseName = getBaseName(index);
|
||||
const QString filePath = dir.absoluteFilePath(baseName);
|
||||
QVariantMap itemData = index.data(contentType::data).toMap();
|
||||
QVariantMap oldMimeToExtension = itemData.value(mimeExtensionMap).toMap();
|
||||
QVariantMap mimeToExtension;
|
||||
QVariantMap dataMapUnknown;
|
||||
|
||||
const QVariantMap noSaveData = itemData.value(mimeNoSave).toMap();
|
||||
|
||||
for ( const auto &format : itemData.keys() ) {
|
||||
if ( format.startsWith(COPYQ_MIME_PREFIX_ITEMSYNC) )
|
||||
continue; // skip internal data
|
||||
|
||||
const QByteArray bytes = itemData[format].toByteArray();
|
||||
const Hash hash = calculateHash(bytes);
|
||||
|
||||
if ( noSaveData.contains(format) && noSaveData[format].toByteArray() == hash ) {
|
||||
itemData.remove(format);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool hasFile = oldMimeToExtension.contains(format);
|
||||
const QString ext = hasFile ? oldMimeToExtension[format].toString()
|
||||
: findByFormat(format, m_formatSettings);
|
||||
|
||||
if ( !hasFile && ext.isEmpty() ) {
|
||||
dataMapUnknown.insert(format, bytes);
|
||||
} else {
|
||||
mimeToExtension.insert(format, ext);
|
||||
const Hash oldHash = indexData(index).formatHash.value(format);
|
||||
if ( !saveItemFile(filePath + ext, bytes, &existingFiles, hash != oldHash) )
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for ( QVariantMap::const_iterator it = oldMimeToExtension.begin();
|
||||
it != oldMimeToExtension.end(); ++it )
|
||||
{
|
||||
if ( it.key().startsWith(mimeNoFormat) )
|
||||
mimeToExtension.insert( it.key(), it.value() );
|
||||
}
|
||||
|
||||
if ( mimeToExtension.isEmpty() || !dataMapUnknown.isEmpty() ) {
|
||||
mimeToExtension.insert(mimeUnknownFormats, dataFileSuffix);
|
||||
QByteArray data = serializeData(dataMapUnknown);
|
||||
if ( !saveItemFile(filePath + dataFileSuffix, data, &existingFiles) )
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !noSaveData.isEmpty() || mimeToExtension != oldMimeToExtension ) {
|
||||
itemData.remove(mimeNoSave);
|
||||
|
||||
for ( const auto &format : mimeToExtension.keys() )
|
||||
oldMimeToExtension.remove(format);
|
||||
|
||||
itemData.insert(mimeExtensionMap, mimeToExtension);
|
||||
updateIndexData(index, itemData);
|
||||
|
||||
// Remove files of removed formats.
|
||||
removeFormatFiles(filePath, oldMimeToExtension);
|
||||
}
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
bool FileWatcher::renameToUnique(const QDir &dir, const QStringList &baseNames, QString *name)
|
||||
{
|
||||
if ( name->isEmpty() ) {
|
||||
*name = "copyq_0000";
|
||||
} else {
|
||||
// Replace/remove unsafe characters.
|
||||
name->replace( QRegExp("/|\\\\|^\\."), QString("_") );
|
||||
name->remove( QRegExp("\\n|\\r") );
|
||||
}
|
||||
|
||||
const QStringList fileNames = dir.entryList();
|
||||
|
||||
if ( isUniqueBaseName(*name, fileNames, baseNames) )
|
||||
return true;
|
||||
|
||||
QString ext;
|
||||
QString baseName;
|
||||
getBaseNameAndExtension(*name, &baseName, &ext, m_formatSettings);
|
||||
|
||||
int i = 0;
|
||||
int fieldWidth = 0;
|
||||
|
||||
QRegExp re("\\d+$");
|
||||
if ( baseName.indexOf(re) != -1 ) {
|
||||
const QString num = re.cap(0);
|
||||
i = num.toInt();
|
||||
fieldWidth = num.size();
|
||||
baseName = baseName.mid( 0, baseName.size() - fieldWidth );
|
||||
} else {
|
||||
baseName.append('-');
|
||||
}
|
||||
|
||||
QString newName;
|
||||
do {
|
||||
if (i >= 99999)
|
||||
return false;
|
||||
newName = baseName + QString("%1").arg(++i, fieldWidth, 10, QChar('0')) + ext;
|
||||
} while ( !isUniqueBaseName(newName, fileNames, baseNames) );
|
||||
|
||||
*name = newName;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileWatcher::renameMoveCopy(const QDir &dir, const QList<QModelIndex> &indexList)
|
||||
{
|
||||
QStringList baseNames;
|
||||
|
||||
for (const auto &index : indexList) {
|
||||
if ( !index.isValid() )
|
||||
continue;
|
||||
|
||||
IndexDataList::iterator it = findIndexData(index);
|
||||
const QString olderBaseName = (it != m_indexData.end()) ? it->baseName : QString();
|
||||
const QString oldBaseName = getBaseName(index);
|
||||
QString baseName = oldBaseName;
|
||||
|
||||
bool newItem = olderBaseName.isEmpty();
|
||||
bool itemRenamed = olderBaseName != baseName;
|
||||
if ( newItem || itemRenamed ) {
|
||||
if ( !renameToUnique(dir, baseNames, &baseName) )
|
||||
return false;
|
||||
itemRenamed = olderBaseName != baseName;
|
||||
baseNames.append(baseName);
|
||||
}
|
||||
|
||||
QVariantMap itemData = index.data(contentType::data).toMap();
|
||||
const QString syncPath = itemData.value(mimeSyncPath).toString();
|
||||
bool copyFilesFromOtherTab = !syncPath.isEmpty() && syncPath != m_path;
|
||||
|
||||
if (copyFilesFromOtherTab || itemRenamed) {
|
||||
const QVariantMap mimeToExtension = itemData.value(mimeExtensionMap).toMap();
|
||||
const QString newBasePath = m_path + '/' + baseName;
|
||||
|
||||
if ( !syncPath.isEmpty() ) {
|
||||
copyFormatFiles(syncPath + '/' + oldBaseName, newBasePath, mimeToExtension);
|
||||
} else {
|
||||
// Move files.
|
||||
if ( !olderBaseName.isEmpty() )
|
||||
moveFormatFiles(m_path + '/' + olderBaseName, newBasePath, mimeToExtension);
|
||||
}
|
||||
|
||||
itemData.remove(mimeSyncPath);
|
||||
itemData.insert(mimeBaseName, baseName);
|
||||
updateIndexData(index, itemData);
|
||||
|
||||
if ( oldBaseName.isEmpty() && itemData.contains(mimeUriList) ) {
|
||||
if ( copyFilesFromUriList(itemData[mimeUriList].toByteArray(), index.row(), baseNames) )
|
||||
m_model->removeRow(index.row());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileWatcher::updateDataAndWatchFile(const QDir &dir, const BaseNameExtensions &baseNameWithExts,
|
||||
QVariantMap *dataMap, QVariantMap *mimeToExtension)
|
||||
{
|
||||
const QString basePath = dir.absoluteFilePath(baseNameWithExts.baseName);
|
||||
|
||||
for (const auto &ext : baseNameWithExts.exts) {
|
||||
Q_ASSERT( !ext.format.isEmpty() );
|
||||
|
||||
const QString fileName = basePath + ext.extension;
|
||||
|
||||
QFile f( dir.absoluteFilePath(fileName) );
|
||||
if ( !f.open(QIODevice::ReadOnly) )
|
||||
continue;
|
||||
|
||||
if ( ext.extension == dataFileSuffix && deserializeData(dataMap, f.readAll()) ) {
|
||||
mimeToExtension->insert(mimeUnknownFormats, dataFileSuffix);
|
||||
} else if ( f.size() > sizeLimit || ext.format.startsWith(mimeNoFormat)
|
||||
|| dataMap->contains(ext.format) )
|
||||
{
|
||||
mimeToExtension->insert(mimeNoFormat + ext.extension, ext.extension);
|
||||
} else {
|
||||
dataMap->insert(ext.format, f.readAll());
|
||||
mimeToExtension->insert(ext.format, ext.extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FileWatcher::copyFilesFromUriList(const QByteArray &uriData, int targetRow, const QStringList &baseNames)
|
||||
{
|
||||
QMimeData tmpData;
|
||||
tmpData.setData(mimeUriList, uriData);
|
||||
|
||||
bool copied = false;
|
||||
|
||||
const QDir dir(m_path);
|
||||
|
||||
for ( const auto &url : tmpData.urls() ) {
|
||||
if ( url.isLocalFile() ) {
|
||||
QFile f(url.toLocalFile());
|
||||
|
||||
if (f.exists()) {
|
||||
QString extName;
|
||||
QString baseName;
|
||||
getBaseNameAndExtension( QFileInfo(f).fileName(), &baseName, &extName,
|
||||
m_formatSettings );
|
||||
|
||||
if ( renameToUnique(dir, baseNames, &baseName) ) {
|
||||
const QString targetFilePath = dir.absoluteFilePath(baseName + extName);
|
||||
f.copy(targetFilePath);
|
||||
Ext ext;
|
||||
if ( m_model->rowCount() < m_maxItems
|
||||
&& getBaseNameExtension(targetFilePath, m_formatSettings, &baseName, &ext) )
|
||||
{
|
||||
BaseNameExtensions baseNameExts(baseName, QList<Ext>() << ext);
|
||||
createItemFromFiles( QDir(m_path), baseNameExts, targetRow );
|
||||
copied = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef FILEWATCHER_H
|
||||
#define FILEWATCHER_H
|
||||
|
||||
#include "common/mimetypes.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QPersistentModelIndex>
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
#include <QVector>
|
||||
|
||||
class QAbstractItemModel;
|
||||
class QDir;
|
||||
|
||||
struct Ext;
|
||||
struct BaseNameExtensions;
|
||||
|
||||
#define COPYQ_MIME_PREFIX_ITEMSYNC COPYQ_MIME_PREFIX "itemsync-"
|
||||
extern const char mimeExtensionMap[];
|
||||
extern const char mimeBaseName[];
|
||||
extern const char mimeNoSave[];
|
||||
extern const char mimeSyncPath[];
|
||||
extern const char mimeNoFormat[];
|
||||
extern const char mimeUnknownFormats[];
|
||||
|
||||
struct FileFormat {
|
||||
bool isValid() const { return !extensions.isEmpty(); }
|
||||
QStringList extensions;
|
||||
QString itemMime;
|
||||
QString icon;
|
||||
};
|
||||
|
||||
using BaseNameExtensionsList = QList<BaseNameExtensions>;
|
||||
|
||||
using Hash = QByteArray;
|
||||
|
||||
class FileWatcher : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static QString getBaseName(const QModelIndex &index);
|
||||
|
||||
/**
|
||||
* Return true only if base name is empty or it matches the internal format.
|
||||
*/
|
||||
static bool isOwnBaseName(const QString &baseName);
|
||||
|
||||
static void removeFilesForRemovedIndex(const QString &tabPath, const QModelIndex &index);
|
||||
|
||||
static Hash calculateHash(const QByteArray &bytes);
|
||||
|
||||
FileWatcher(const QString &path, const QStringList &paths, QAbstractItemModel *model,
|
||||
int maxItems, const QList<FileFormat> &formatSettings, QObject *parent);
|
||||
|
||||
const QString &path() const { return m_path; }
|
||||
|
||||
bool isValid() const { return m_valid; }
|
||||
|
||||
QAbstractItemModel *model() const { return m_model; }
|
||||
|
||||
public slots:
|
||||
bool lock();
|
||||
|
||||
void unlock();
|
||||
|
||||
bool createItemFromFiles(const QDir &dir, const BaseNameExtensions &baseNameWithExts, int targetRow);
|
||||
|
||||
void createItemsFromFiles(const QDir &dir, const BaseNameExtensionsList &fileList);
|
||||
|
||||
/**
|
||||
* Check for new files.
|
||||
*/
|
||||
void updateItems();
|
||||
|
||||
private slots:
|
||||
void onRowsInserted(const QModelIndex &, int first, int last);
|
||||
|
||||
void onDataChanged(const QModelIndex &a, const QModelIndex &b);
|
||||
|
||||
void onRowsRemoved(const QModelIndex &, int first, int last);
|
||||
|
||||
private:
|
||||
struct IndexData {
|
||||
QPersistentModelIndex index;
|
||||
QString baseName;
|
||||
QMap<QString, Hash> formatHash;
|
||||
|
||||
IndexData() {}
|
||||
explicit IndexData(const QModelIndex &index) : index(index) {}
|
||||
bool operator==(const QModelIndex &otherIndex) const { return otherIndex == index; }
|
||||
};
|
||||
|
||||
using IndexDataList = QVector<IndexData>;
|
||||
|
||||
IndexDataList::iterator findIndexData(const QModelIndex &index);
|
||||
|
||||
IndexData &indexData(const QModelIndex &index);
|
||||
|
||||
bool createItem(const QVariantMap &dataMap, int targetRow);
|
||||
|
||||
void updateIndexData(const QModelIndex &index, const QVariantMap &itemData);
|
||||
|
||||
QList<QModelIndex> indexList(int first, int last);
|
||||
|
||||
void saveItems(int first, int last);
|
||||
|
||||
bool renameToUnique(const QDir &dir, const QStringList &baseNames, QString *name);
|
||||
|
||||
bool renameMoveCopy(const QDir &dir, const QList<QModelIndex> &indexList);
|
||||
|
||||
void updateDataAndWatchFile(
|
||||
const QDir &dir, const BaseNameExtensions &baseNameWithExts,
|
||||
QVariantMap *dataMap, QVariantMap *mimeToExtension);
|
||||
|
||||
bool copyFilesFromUriList(const QByteArray &uriData, int targetRow, const QStringList &baseNames);
|
||||
|
||||
QPointer<QAbstractItemModel> m_model;
|
||||
QTimer m_updateTimer;
|
||||
const QList<FileFormat> &m_formatSettings;
|
||||
QString m_path;
|
||||
bool m_valid;
|
||||
IndexDataList m_indexData;
|
||||
int m_maxItems;
|
||||
};
|
||||
|
||||
#endif // FILEWATCHER_H
|
@ -0,0 +1,870 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemsync.h"
|
||||
#include "ui_itemsyncsettings.h"
|
||||
|
||||
#include "filewatcher.h"
|
||||
|
||||
#include "common/log.h"
|
||||
#include "common/mimetypes.h"
|
||||
#include "common/contenttype.h"
|
||||
#include "gui/iconselectbutton.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/iconfont.h"
|
||||
#include "gui/iconwidget.h"
|
||||
|
||||
#ifdef HAS_TESTS
|
||||
# include "tests/itemsynctests.h"
|
||||
#endif
|
||||
|
||||
#include <QBoxLayout>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
#include <QTimer>
|
||||
#include <QtPlugin>
|
||||
#include <QUrl>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
namespace syncTabsTableColumns {
|
||||
enum {
|
||||
tabName,
|
||||
path,
|
||||
browse
|
||||
};
|
||||
}
|
||||
|
||||
namespace formatSettingsTableColumns {
|
||||
enum {
|
||||
formats,
|
||||
itemMime,
|
||||
icon
|
||||
};
|
||||
}
|
||||
|
||||
const int currentVersion = 1;
|
||||
const char dataFileHeader[] = "CopyQ_itemsync_tab";
|
||||
|
||||
const char configVersion[] = "copyq_itemsync_version";
|
||||
const char configSyncTabs[] = "sync_tabs";
|
||||
const char configFormatSettings[] = "format_settings";
|
||||
|
||||
const char tabConfigSavedFiles[] = "saved_files";
|
||||
|
||||
bool readConfigHeader(QDataStream *stream)
|
||||
{
|
||||
QString header;
|
||||
*stream >> header;
|
||||
return header == dataFileHeader;
|
||||
}
|
||||
|
||||
bool readConfig(QIODevice *file, QVariantMap *config)
|
||||
{
|
||||
QDataStream stream(file);
|
||||
if ( !readConfigHeader(&stream) )
|
||||
return false;
|
||||
|
||||
stream >> *config;
|
||||
|
||||
return stream.status() == QDataStream::Ok
|
||||
&& config->value(configVersion, 0).toInt() == currentVersion;
|
||||
}
|
||||
|
||||
void writeConfiguration(QIODevice *file, const QStringList &savedFiles)
|
||||
{
|
||||
QVariantMap config;
|
||||
config.insert(configVersion, currentVersion);
|
||||
config.insert(tabConfigSavedFiles, savedFiles);
|
||||
|
||||
QDataStream stream(file);
|
||||
stream.setVersion(QDataStream::Qt_4_7);
|
||||
stream << QString(dataFileHeader);
|
||||
stream << config;
|
||||
}
|
||||
|
||||
void setHeaderSectionResizeMode(QHeaderView *header, int logicalIndex, QHeaderView::ResizeMode mode)
|
||||
{
|
||||
#if QT_VERSION < 0x050000
|
||||
header->setResizeMode(logicalIndex, mode);
|
||||
#else
|
||||
header->setSectionResizeMode(logicalIndex, mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString iconFromId(int id)
|
||||
{
|
||||
return id != -1 ? QString(QChar(id)) : QString();
|
||||
}
|
||||
|
||||
QPushButton *createBrowseButton()
|
||||
{
|
||||
std::unique_ptr<QPushButton> button(new QPushButton);
|
||||
button->setFont( iconFont() );
|
||||
button->setText( iconFromId(IconFolderOpen) );
|
||||
button->setToolTip( ItemSyncLoader::tr("Browse...",
|
||||
"Button text for opening file dialog to select synchronization directory") );
|
||||
return button.release();
|
||||
}
|
||||
|
||||
bool hasVideoExtension(const QString &ext)
|
||||
{
|
||||
return ext == "avi"
|
||||
|| ext == "mkv"
|
||||
|| ext == "mp4"
|
||||
|| ext == "mpg"
|
||||
|| ext == "mpeg"
|
||||
|| ext == "ogv"
|
||||
|| ext == "flv";
|
||||
}
|
||||
|
||||
bool hasAudioExtension(const QString &ext)
|
||||
{
|
||||
return ext == "mp3"
|
||||
|| ext == "wav"
|
||||
|| ext == "ogg"
|
||||
|| ext == "m4a";
|
||||
}
|
||||
|
||||
bool hasImageExtension(const QString &ext)
|
||||
{
|
||||
return ext == "png"
|
||||
|| ext == "jpg"
|
||||
|| ext == "gif"
|
||||
|| ext == "bmp"
|
||||
|| ext == "svg"
|
||||
|| ext == "tga"
|
||||
|| ext == "tiff"
|
||||
|| ext == "psd"
|
||||
|| ext == "xcf"
|
||||
|| ext == "ico"
|
||||
|| ext == "pbm"
|
||||
|| ext == "ppm"
|
||||
|| ext == "eps"
|
||||
|| ext == "pcx"
|
||||
|| ext == "jpx"
|
||||
|| ext == "jp2";
|
||||
}
|
||||
|
||||
bool hasArchiveExtension(const QString &ext)
|
||||
{
|
||||
return ext == "zip"
|
||||
|| ext == "7z"
|
||||
|| ext == "tar"
|
||||
|| ext == "rar"
|
||||
|| QRegExp("r\\d\\d").exactMatch(ext)
|
||||
|| ext == "arj";
|
||||
}
|
||||
|
||||
bool hasTextExtension(const QString &ext)
|
||||
{
|
||||
return ext == "txt"
|
||||
|| ext == "log"
|
||||
|| ext == "xml"
|
||||
|| ext == "html"
|
||||
|| ext == "htm"
|
||||
|| ext == "pdf"
|
||||
|| ext == "doc"
|
||||
|| ext == "docx"
|
||||
|| ext == "odt"
|
||||
|| ext == "xls"
|
||||
|| ext == "rtf"
|
||||
|| ext == "csv"
|
||||
|| ext == "ppt";
|
||||
}
|
||||
|
||||
int iconFromBaseNameExtensionHelper(const QString &baseName)
|
||||
{
|
||||
const int i = baseName.lastIndexOf('.');
|
||||
if (i != -1) {
|
||||
const QString ext = baseName.mid(i + 1);
|
||||
if ( hasVideoExtension(ext) )
|
||||
return IconPlayCircle;
|
||||
if ( hasAudioExtension(ext) )
|
||||
return IconVolumeUp;
|
||||
if ( hasImageExtension(ext) )
|
||||
return IconCamera;
|
||||
if ( hasArchiveExtension(ext) )
|
||||
return IconFileText;
|
||||
if ( hasTextExtension(ext) )
|
||||
return IconFileText;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int iconFromMimeHelper(const QString &format)
|
||||
{
|
||||
if ( format.startsWith("video/") )
|
||||
return IconPlayCircle;
|
||||
if ( format.startsWith("audio/") )
|
||||
return IconVolumeUp;
|
||||
if ( format.startsWith("image/") )
|
||||
return IconCamera;
|
||||
if ( format.startsWith("text/") )
|
||||
return IconFileText;
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString iconFromUserExtension(const QStringList &fileNames, const QList<FileFormat> &formatSettings)
|
||||
{
|
||||
for ( const auto &format : formatSettings ) {
|
||||
if ( format.icon.isEmpty() )
|
||||
continue;
|
||||
|
||||
for (const auto &ext : format.extensions) {
|
||||
for (const auto &fileName : fileNames) {
|
||||
if ( fileName.endsWith(ext) )
|
||||
return format.icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString iconFromMime(const QString &format)
|
||||
{
|
||||
return iconFromId(iconFromMimeHelper(format));
|
||||
}
|
||||
|
||||
QString iconForItem(const QModelIndex &index, const QList<FileFormat> &formatSettings)
|
||||
{
|
||||
const QString baseName = FileWatcher::getBaseName(index);
|
||||
const QVariantMap dataMap = index.data(contentType::data).toMap();
|
||||
const QVariantMap mimeToExtension = dataMap.value(mimeExtensionMap).toMap();
|
||||
|
||||
QStringList fileNames;
|
||||
for ( const auto &format : mimeToExtension.keys() ) {
|
||||
// Don't change icon for notes.
|
||||
if (format != mimeItemNotes)
|
||||
fileNames.append( baseName + mimeToExtension[format].toString() );
|
||||
}
|
||||
|
||||
// Try to get user icon from file extension.
|
||||
const QString icon = iconFromUserExtension(fileNames, formatSettings);
|
||||
if ( !icon.isEmpty() )
|
||||
return icon;
|
||||
|
||||
// Try to get default icon from MIME type.
|
||||
for ( const auto &format : dataMap.keys() ) {
|
||||
const auto mimeIcon = iconFromMime(format);
|
||||
if ( !mimeIcon.isEmpty() )
|
||||
return mimeIcon;
|
||||
}
|
||||
|
||||
// Try to get default icon from file extension.
|
||||
for (const auto &fileName : fileNames) {
|
||||
const int id = iconFromBaseNameExtensionHelper(fileName);
|
||||
if (id != -1)
|
||||
return iconFromId(id);
|
||||
}
|
||||
|
||||
// Return icon for unknown files.
|
||||
return iconFromId(IconFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true only if the item was created by CopyQ
|
||||
* (i.e. has no file assigned or the file name matches internal format).
|
||||
*/
|
||||
bool isOwnItem(const QModelIndex &index)
|
||||
{
|
||||
const QString baseName = FileWatcher::getBaseName(index);
|
||||
return baseName.isEmpty() || FileWatcher::isOwnBaseName(baseName);
|
||||
}
|
||||
|
||||
bool containsItemsWithNotOwnedFiles(const QList<QModelIndex> &indexList)
|
||||
{
|
||||
for (const auto &index : indexList) {
|
||||
if ( !isOwnItem(index) )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void fixUserExtensions(QStringList *exts)
|
||||
{
|
||||
for (int i = 0; i < exts->size(); ++i) {
|
||||
QString &ext = (*exts)[i];
|
||||
if ( !ext.startsWith('.') )
|
||||
ext.prepend('.');
|
||||
// Use "_user.dat" instead of "*.dat" to avoid collisions with extension "_copy.dat"
|
||||
// internally used to store data of unknown MIME type.
|
||||
if ( ext.toLower().endsWith(".dat") )
|
||||
ext.insert(ext.size() - 4, "_user");
|
||||
// Remove invalid extensions containing path separator.
|
||||
if ( ext.contains('/') )
|
||||
exts->removeAt(i--);
|
||||
}
|
||||
}
|
||||
|
||||
void fixUserMimeType(QString *mimeType)
|
||||
{
|
||||
// Don't allow user to override internal formats.
|
||||
if ( mimeType->startsWith(COPYQ_MIME_PREFIX_ITEMSYNC) )
|
||||
mimeType->clear();
|
||||
}
|
||||
|
||||
void setNormalStretchFixedColumns(QTableWidget *table, int normalColumn, int stretchColumn, int fixedColumn)
|
||||
{
|
||||
QHeaderView *header = table->horizontalHeader();
|
||||
setHeaderSectionResizeMode(header, stretchColumn, QHeaderView::Stretch);
|
||||
setHeaderSectionResizeMode(header, fixedColumn, QHeaderView::Fixed);
|
||||
header->resizeSection(fixedColumn, table->rowHeight(0));
|
||||
table->resizeColumnToContents(normalColumn);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemSync::ItemSync(const QString &label, const QString &icon, ItemWidget *childItem)
|
||||
: QWidget( childItem->widget()->parentWidget() )
|
||||
, ItemWidget(this)
|
||||
, m_label( new QTextEdit(this) )
|
||||
, m_icon( new IconWidget(icon, this) )
|
||||
, m_childItem(childItem)
|
||||
{
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->setSizeConstraint(QLayout::SetMinimumSize);
|
||||
|
||||
auto labelLayout = new QHBoxLayout;
|
||||
connect(layout, SIGNAL(destroyed()), labelLayout, SLOT(deleteLater()));
|
||||
labelLayout->setContentsMargins(0, 0, 0, 0);
|
||||
labelLayout->setSpacing(0);
|
||||
|
||||
labelLayout->addWidget(m_icon);
|
||||
labelLayout->addWidget(m_label);
|
||||
labelLayout->addStretch();
|
||||
|
||||
layout->addLayout(labelLayout);
|
||||
|
||||
QWidget *w = m_childItem->widget();
|
||||
layout->addWidget(w);
|
||||
w->setObjectName("item_child");
|
||||
w->setParent(this);
|
||||
|
||||
m_label->setObjectName("item_child");
|
||||
|
||||
m_label->document()->setDefaultFont(font());
|
||||
|
||||
QTextOption option = m_label->document()->defaultTextOption();
|
||||
option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
||||
m_label->document()->setDefaultTextOption(option);
|
||||
|
||||
m_label->setReadOnly(true);
|
||||
m_label->setUndoRedoEnabled(false);
|
||||
|
||||
m_label->setFocusPolicy(Qt::NoFocus);
|
||||
m_label->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_label->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_label->setFrameStyle(QFrame::NoFrame);
|
||||
m_label->setContextMenuPolicy(Qt::NoContextMenu);
|
||||
|
||||
m_label->viewport()->installEventFilter(this);
|
||||
|
||||
m_label->setPlainText(label);
|
||||
}
|
||||
|
||||
void ItemSync::setCurrent(bool current)
|
||||
{
|
||||
if (m_childItem != nullptr)
|
||||
m_childItem->setCurrent(current);
|
||||
}
|
||||
|
||||
void ItemSync::highlight(const QRegExp &re, const QFont &highlightFont, const QPalette &highlightPalette)
|
||||
{
|
||||
m_childItem->setHighlight(re, highlightFont, highlightPalette);
|
||||
|
||||
QList<QTextEdit::ExtraSelection> selections;
|
||||
|
||||
if ( !re.isEmpty() ) {
|
||||
QTextEdit::ExtraSelection selection;
|
||||
selection.format.setBackground( highlightPalette.base() );
|
||||
selection.format.setForeground( highlightPalette.text() );
|
||||
selection.format.setFont(highlightFont);
|
||||
|
||||
QTextCursor cur = m_label->document()->find(re);
|
||||
int a = cur.position();
|
||||
while ( !cur.isNull() ) {
|
||||
if ( cur.hasSelection() ) {
|
||||
selection.cursor = cur;
|
||||
selections.append(selection);
|
||||
} else {
|
||||
cur.movePosition(QTextCursor::NextCharacter);
|
||||
}
|
||||
cur = m_label->document()->find(re, cur);
|
||||
int b = cur.position();
|
||||
if (a == b) {
|
||||
cur.movePosition(QTextCursor::NextCharacter);
|
||||
cur = m_label->document()->find(re, cur);
|
||||
b = cur.position();
|
||||
if (a == b) break;
|
||||
}
|
||||
a = b;
|
||||
}
|
||||
}
|
||||
|
||||
m_label->setExtraSelections(selections);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
QWidget *ItemSync::createEditor(QWidget *parent) const
|
||||
{
|
||||
return m_childItem->createEditor(parent);
|
||||
}
|
||||
|
||||
void ItemSync::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
return m_childItem->setEditorData(editor, index);
|
||||
}
|
||||
|
||||
void ItemSync::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
|
||||
{
|
||||
return m_childItem->setModelData(editor, model, index);
|
||||
}
|
||||
|
||||
bool ItemSync::hasChanges(QWidget *editor) const
|
||||
{
|
||||
return m_childItem->hasChanges(editor);
|
||||
}
|
||||
|
||||
QObject *ItemSync::createExternalEditor(const QModelIndex &index, QWidget *parent) const
|
||||
{
|
||||
return m_childItem->createExternalEditor(index, parent);
|
||||
}
|
||||
|
||||
void ItemSync::updateSize(const QSize &maximumSize, int idealWidth)
|
||||
{
|
||||
setMaximumSize(maximumSize);
|
||||
|
||||
const int w = idealWidth - m_icon->width() - 8;
|
||||
QTextDocument *doc = m_label->document();
|
||||
doc->setTextWidth(w);
|
||||
m_label->setFixedSize( w, static_cast<int>(doc->size().height()) );
|
||||
|
||||
m_childItem->updateSize(maximumSize, idealWidth);
|
||||
|
||||
adjustSize();
|
||||
setFixedSize(sizeHint());
|
||||
}
|
||||
|
||||
bool ItemSync::eventFilter(QObject *, QEvent *event)
|
||||
{
|
||||
return ItemWidget::filterMouseEvents(m_label, event);
|
||||
}
|
||||
|
||||
ItemSyncSaver::ItemSyncSaver(const QString &tabPath)
|
||||
: m_tabPath(tabPath)
|
||||
, m_watcher(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ItemSyncSaver::ItemSyncSaver(
|
||||
QAbstractItemModel *model,
|
||||
const QString &tabPath,
|
||||
const QString &path,
|
||||
const QStringList &files,
|
||||
int maxItems,
|
||||
const QList<FileFormat> &formatSettings)
|
||||
: m_tabPath(tabPath)
|
||||
, m_watcher(new FileWatcher(path, files, model, maxItems, formatSettings, this))
|
||||
{
|
||||
}
|
||||
|
||||
bool ItemSyncSaver::saveItems(const QString &tabName, const QAbstractItemModel &model, QIODevice *file)
|
||||
{
|
||||
// Don't save items if path is empty.
|
||||
if (!m_watcher) {
|
||||
writeConfiguration(file, QStringList());
|
||||
return true;
|
||||
}
|
||||
|
||||
const QString path = m_watcher->path();
|
||||
QStringList savedFiles;
|
||||
|
||||
if ( !m_watcher->isValid() ) {
|
||||
log( tr("Failed to synchronize tab \"%1\" with directory \"%2\"!")
|
||||
.arg(tabName)
|
||||
.arg(path),
|
||||
LogError );
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir dir(path);
|
||||
|
||||
for (int row = 0; row < model.rowCount(); ++row) {
|
||||
const QModelIndex index = model.index(row, 0);
|
||||
const QVariantMap itemData = index.data(contentType::data).toMap();
|
||||
const QVariantMap mimeToExtension = itemData.value(mimeExtensionMap).toMap();
|
||||
const QString baseName = FileWatcher::getBaseName(index);
|
||||
const QString filePath = dir.absoluteFilePath(baseName);
|
||||
|
||||
for (const auto &ext : mimeToExtension.values())
|
||||
savedFiles.prepend( filePath + ext.toString() );
|
||||
}
|
||||
|
||||
writeConfiguration(file, savedFiles);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ItemSyncSaver::canRemoveItems(const QList<QModelIndex> &indexList, QString *error)
|
||||
{
|
||||
if ( !containsItemsWithNotOwnedFiles(indexList) )
|
||||
return true;
|
||||
|
||||
if (error) {
|
||||
*error = "Removing synchronized items with assigned files from script is not allowed (remove the files instead)";
|
||||
return false;
|
||||
}
|
||||
|
||||
return QMessageBox::question(
|
||||
QApplication::activeWindow(), tr("Remove Items?"),
|
||||
tr("Do you really want to <strong>remove items and associated files</strong>?"),
|
||||
QMessageBox::No | QMessageBox::Yes,
|
||||
QMessageBox::Yes ) == QMessageBox::Yes;
|
||||
}
|
||||
|
||||
bool ItemSyncSaver::canMoveItems(const QList<QModelIndex> &)
|
||||
{
|
||||
// Don't remove items if moved out of list.
|
||||
// Items will be automatically removed if underlying files are deleted by the move operation.
|
||||
return false;
|
||||
}
|
||||
|
||||
void ItemSyncSaver::itemsRemovedByUser(const QList<QModelIndex> &indexList)
|
||||
{
|
||||
if ( m_tabPath.isEmpty() )
|
||||
return;
|
||||
|
||||
// Remove unneeded files (remaining records in the hash map).
|
||||
for (const auto &index : indexList)
|
||||
FileWatcher::removeFilesForRemovedIndex(m_tabPath, index);
|
||||
}
|
||||
|
||||
QVariantMap ItemSyncSaver::copyItem(const QAbstractItemModel &, const QVariantMap &itemData)
|
||||
{
|
||||
QVariantMap copiedItemData = itemData;
|
||||
copiedItemData.insert(mimeSyncPath, m_tabPath);
|
||||
|
||||
// Add text/plain and text/uri-list if not present.
|
||||
bool updateUriData = !copiedItemData.contains(mimeUriList);
|
||||
bool updateTextData = !copiedItemData.contains(mimeText);
|
||||
if (updateUriData || updateTextData) {
|
||||
QByteArray uriData;
|
||||
QByteArray textData;
|
||||
|
||||
const QVariantMap mimeToExtension = itemData.value(mimeExtensionMap).toMap();
|
||||
const QString basePath = m_tabPath + '/' + itemData.value(mimeBaseName).toString();
|
||||
|
||||
for ( const auto &format : mimeToExtension.keys() ) {
|
||||
const QString ext = mimeToExtension[format].toString();
|
||||
const QString filePath = basePath + ext;
|
||||
|
||||
if (updateUriData) {
|
||||
if ( !uriData.isEmpty() )
|
||||
uriData.append("\n");
|
||||
uriData.append( QUrl::fromLocalFile(filePath).toEncoded() );
|
||||
}
|
||||
|
||||
if (updateTextData) {
|
||||
if ( !textData.isEmpty() )
|
||||
textData.append("\n");
|
||||
textData.append( filePath.toUtf8()
|
||||
.replace('\\', "\\\\")
|
||||
.replace('\n', "\\n")
|
||||
.replace('\r', "\\r") );
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap noSaveData;
|
||||
if (updateUriData) {
|
||||
noSaveData.insert(mimeUriList, FileWatcher::calculateHash(uriData));
|
||||
copiedItemData.insert(mimeUriList, uriData);
|
||||
}
|
||||
if (updateTextData) {
|
||||
noSaveData.insert(mimeText, FileWatcher::calculateHash(textData));
|
||||
copiedItemData.insert(mimeText, textData);
|
||||
}
|
||||
copiedItemData.insert(mimeNoSave, noSaveData);
|
||||
}
|
||||
|
||||
return copiedItemData;
|
||||
}
|
||||
|
||||
ItemSyncScriptable::ItemSyncScriptable(
|
||||
const ItemSyncTabPaths &tabPaths, QObject *parent)
|
||||
: ItemScriptable(parent)
|
||||
{
|
||||
for (const auto &key : tabPaths.keys())
|
||||
m_tabPaths.insert(key, tabPaths[key]);
|
||||
}
|
||||
|
||||
QVariantMap ItemSyncScriptable::getTabPaths() const
|
||||
{
|
||||
return m_tabPaths;
|
||||
}
|
||||
|
||||
ItemSyncLoader::ItemSyncLoader()
|
||||
{
|
||||
}
|
||||
|
||||
ItemSyncLoader::~ItemSyncLoader() = default;
|
||||
|
||||
QVariantMap ItemSyncLoader::applySettings()
|
||||
{
|
||||
// Apply settings from tab sync path table.
|
||||
QTableWidget *t = ui->tableWidgetSyncTabs;
|
||||
QStringList tabPaths;
|
||||
m_tabPaths.clear();
|
||||
for (int row = 0; row < t->rowCount(); ++row) {
|
||||
const QString tabName = t->item(row, syncTabsTableColumns::tabName)->text();
|
||||
if ( !tabName.isEmpty() ) {
|
||||
const QString tabPath = t->item(row, syncTabsTableColumns::path)->text();
|
||||
tabPaths << tabName << tabPath;
|
||||
m_tabPaths.insert(tabName, tabPath);
|
||||
}
|
||||
}
|
||||
m_settings.insert(configSyncTabs, tabPaths);
|
||||
|
||||
// Apply settings from file format table.
|
||||
t = ui->tableWidgetFormatSettings;
|
||||
QVariantList formatSettings;
|
||||
m_formatSettings.clear();
|
||||
for (int row = 0; row < t->rowCount(); ++row) {
|
||||
FileFormat fileFormat;
|
||||
fileFormat.extensions = t->item(row, formatSettingsTableColumns::formats)->text()
|
||||
.split( QRegExp("[,;\\s]"), QString::SkipEmptyParts );
|
||||
fileFormat.itemMime = t->item(row, formatSettingsTableColumns::itemMime)->text();
|
||||
if ( fileFormat.extensions.isEmpty() && fileFormat.itemMime.isEmpty() )
|
||||
continue;
|
||||
fileFormat.icon = t->cellWidget(row, formatSettingsTableColumns::icon)
|
||||
->property("currentIcon").toString();
|
||||
|
||||
QVariantMap format;
|
||||
format["formats"] = fileFormat.extensions;
|
||||
format["itemMime"] = fileFormat.itemMime;
|
||||
format["icon"] = fileFormat.icon;
|
||||
formatSettings.append(format);
|
||||
|
||||
fixUserExtensions(&fileFormat.extensions);
|
||||
fixUserMimeType(&fileFormat.itemMime);
|
||||
m_formatSettings.append(fileFormat);
|
||||
}
|
||||
m_settings.insert(configFormatSettings, formatSettings);
|
||||
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
void ItemSyncLoader::loadSettings(const QVariantMap &settings)
|
||||
{
|
||||
m_settings = settings;
|
||||
|
||||
m_tabPaths.clear();
|
||||
const QStringList tabPaths = m_settings.value(configSyncTabs).toStringList();
|
||||
for (int i = 0; i < tabPaths.size(); i += 2)
|
||||
m_tabPaths.insert( tabPaths[i], tabPaths.value(i + 1) );
|
||||
|
||||
m_formatSettings.clear();
|
||||
const QVariantList formatSettings = m_settings.value(configFormatSettings).toList();
|
||||
for (const auto &formatSetting : formatSettings) {
|
||||
QVariantMap format = formatSetting.toMap();
|
||||
FileFormat fileFormat;
|
||||
fileFormat.extensions = format.value("formats").toStringList();
|
||||
fileFormat.itemMime = format.value("itemMime").toString();
|
||||
fileFormat.icon = format.value("icon").toString();
|
||||
fixUserExtensions(&fileFormat.extensions);
|
||||
fixUserMimeType(&fileFormat.itemMime);
|
||||
m_formatSettings.append(fileFormat);
|
||||
}
|
||||
}
|
||||
|
||||
QWidget *ItemSyncLoader::createSettingsWidget(QWidget *parent)
|
||||
{
|
||||
ui.reset(new Ui::ItemSyncSettings);
|
||||
QWidget *w = new QWidget(parent);
|
||||
ui->setupUi(w);
|
||||
|
||||
// Init tab sync path table.
|
||||
const QStringList tabPaths = m_settings.value(configSyncTabs).toStringList();
|
||||
QTableWidget *t = ui->tableWidgetSyncTabs;
|
||||
for (int row = 0, i = 0; i < tabPaths.size() + 20; ++row, i += 2) {
|
||||
t->insertRow(row);
|
||||
t->setItem( row, syncTabsTableColumns::tabName, new QTableWidgetItem(tabPaths.value(i)) );
|
||||
t->setItem( row, syncTabsTableColumns::path, new QTableWidgetItem(tabPaths.value(i + 1)) );
|
||||
|
||||
QPushButton *button = createBrowseButton();
|
||||
t->setCellWidget(row, syncTabsTableColumns::browse, button);
|
||||
connect( button, SIGNAL(clicked()), SLOT(onBrowseButtonClicked()) );
|
||||
}
|
||||
setNormalStretchFixedColumns(t, syncTabsTableColumns::tabName, syncTabsTableColumns::path,
|
||||
syncTabsTableColumns::browse);
|
||||
|
||||
// Init file format table.
|
||||
const QVariantList formatSettings = m_settings.value(configFormatSettings).toList();
|
||||
t = ui->tableWidgetFormatSettings;
|
||||
for (int row = 0; row < formatSettings.size() + 10; ++row) {
|
||||
const QVariantMap format = formatSettings.value(row).toMap();
|
||||
const QString formats = format.value("formats").toStringList().join(", ");
|
||||
t->insertRow(row);
|
||||
t->setItem( row, formatSettingsTableColumns::formats, new QTableWidgetItem(formats) );
|
||||
t->setItem( row, formatSettingsTableColumns::itemMime, new QTableWidgetItem(format.value("itemMime").toString()) );
|
||||
|
||||
auto button = new IconSelectButton();
|
||||
button->setCurrentIcon( format.value("icon").toString() );
|
||||
t->setCellWidget(row, formatSettingsTableColumns::icon, button);
|
||||
}
|
||||
setNormalStretchFixedColumns(t, formatSettingsTableColumns::formats,
|
||||
formatSettingsTableColumns::itemMime,
|
||||
formatSettingsTableColumns::icon);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
bool ItemSyncLoader::canLoadItems(QIODevice *file) const
|
||||
{
|
||||
QDataStream stream(file);
|
||||
return readConfigHeader(&stream);
|
||||
}
|
||||
|
||||
bool ItemSyncLoader::canSaveItems(const QString &tabName) const
|
||||
{
|
||||
return m_tabPaths.contains(tabName);
|
||||
}
|
||||
|
||||
ItemSaverPtr ItemSyncLoader::loadItems(const QString &tabName, QAbstractItemModel *model, QIODevice *file, int maxItems)
|
||||
{
|
||||
QVariantMap config;
|
||||
if ( !readConfig(file, &config) )
|
||||
return nullptr;
|
||||
|
||||
const QStringList files = config.value(tabConfigSavedFiles).toStringList();
|
||||
return loadItems(tabName, model, files, maxItems);
|
||||
}
|
||||
|
||||
ItemSaverPtr ItemSyncLoader::initializeTab(const QString &tabName, QAbstractItemModel *model, int maxItems)
|
||||
{
|
||||
return loadItems(tabName, model, QStringList(), maxItems);
|
||||
}
|
||||
|
||||
ItemWidget *ItemSyncLoader::transform(ItemWidget *itemWidget, const QModelIndex &index)
|
||||
{
|
||||
if ( isOwnItem(index) )
|
||||
return nullptr;
|
||||
|
||||
itemWidget->setTagged(true);
|
||||
const QString baseName = FileWatcher::getBaseName(index);
|
||||
return new ItemSync(baseName, iconForItem(index, m_formatSettings), itemWidget);
|
||||
}
|
||||
|
||||
bool ItemSyncLoader::matches(const QModelIndex &index, const QRegExp &re) const
|
||||
{
|
||||
const QVariantMap dataMap = index.data(contentType::data).toMap();
|
||||
const QString text = dataMap.value(mimeBaseName).toString();
|
||||
return re.indexIn(text) != -1;
|
||||
}
|
||||
|
||||
QObject *ItemSyncLoader::tests(const TestInterfacePtr &test) const
|
||||
{
|
||||
#ifdef HAS_TESTS
|
||||
QStringList tabPaths;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
tabPaths.append(ItemSyncTests::testTab(i));
|
||||
tabPaths.append(ItemSyncTests::testDir(i));
|
||||
}
|
||||
|
||||
QVariantList formatSettings;
|
||||
QVariantMap format;
|
||||
|
||||
format["formats"] = QStringList() << "xxx";
|
||||
format["itemMime"] = QString(COPYQ_MIME_PREFIX "test-xxx");
|
||||
format["icon"] = QString(iconFromId(IconTrash));
|
||||
formatSettings << format;
|
||||
|
||||
format["formats"] = QStringList() << "zzz" << ".yyy";
|
||||
format["itemMime"] = QString(COPYQ_MIME_PREFIX "test-zzz");
|
||||
format["icon"] = QString();
|
||||
formatSettings << format;
|
||||
|
||||
QVariantMap settings;
|
||||
settings[configSyncTabs] = tabPaths;
|
||||
settings[configFormatSettings] = formatSettings;
|
||||
|
||||
QObject *tests = new ItemSyncTests(test);
|
||||
tests->setProperty("CopyQ_test_settings", settings);
|
||||
return tests;
|
||||
#else
|
||||
Q_UNUSED(test);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
ItemScriptable *ItemSyncLoader::scriptableObject(QObject *parent)
|
||||
{
|
||||
return new ItemSyncScriptable(m_tabPaths, parent);
|
||||
}
|
||||
|
||||
void ItemSyncLoader::onBrowseButtonClicked()
|
||||
{
|
||||
QTableWidget *t = ui->tableWidgetSyncTabs;
|
||||
|
||||
QObject *button = sender();
|
||||
Q_ASSERT(button != nullptr);
|
||||
|
||||
int row = 0;
|
||||
for ( ; row < t->rowCount() && t->cellWidget(row, syncTabsTableColumns::browse) != button; ++row ) {}
|
||||
Q_ASSERT(row != t->rowCount());
|
||||
|
||||
QTableWidgetItem *item = t->item(row, syncTabsTableColumns::path);
|
||||
const QString path =
|
||||
QFileDialog::getExistingDirectory( t, tr("Open Directory for Synchronization"), item->text() );
|
||||
if ( !path.isEmpty() )
|
||||
item->setText(path);
|
||||
}
|
||||
|
||||
ItemSaverPtr ItemSyncLoader::loadItems(const QString &tabName, QAbstractItemModel *model, const QStringList &files, int maxItems)
|
||||
{
|
||||
const auto tabPath = m_tabPaths.value(tabName);
|
||||
const auto path = files.isEmpty() ? tabPath : QFileInfo(files.first()).absolutePath();
|
||||
if ( path.isEmpty() )
|
||||
return std::make_shared<ItemSyncSaver>(tabPath);
|
||||
|
||||
QDir dir(path);
|
||||
if ( !dir.mkpath(".") ) {
|
||||
emit error( tr("Failed to create synchronization directory"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_shared<ItemSyncSaver>(model, tabPath, dir.path(), files, maxItems, m_formatSettings);
|
||||
}
|
||||
|
||||
Q_EXPORT_PLUGIN2(itemsync, ItemSyncLoader)
|
@ -0,0 +1,194 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMSYNC_H
|
||||
#define ITEMSYNC_H
|
||||
|
||||
#include "gui/icons.h"
|
||||
#include "item/itemwidget.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Ui {
|
||||
class ItemSyncSettings;
|
||||
}
|
||||
|
||||
class QTextEdit;
|
||||
|
||||
class FileWatcher;
|
||||
struct FileFormat;
|
||||
|
||||
using ItemSyncTabPaths = QMap<QString, QString>;
|
||||
|
||||
class ItemSync : public QWidget, public ItemWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ItemSync(const QString &label, const QString &icon, ItemWidget *childItem = nullptr);
|
||||
|
||||
void setCurrent(bool current) override;
|
||||
|
||||
protected:
|
||||
void highlight(const QRegExp &re, const QFont &highlightFont,
|
||||
const QPalette &highlightPalette) override;
|
||||
|
||||
QWidget *createEditor(QWidget *parent) const override;
|
||||
|
||||
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
|
||||
void setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
bool hasChanges(QWidget *editor) const override;
|
||||
|
||||
QObject *createExternalEditor(const QModelIndex &index, QWidget *parent) const override;
|
||||
|
||||
void updateSize(const QSize &maximumSize, int idealWidth) override;
|
||||
|
||||
bool eventFilter(QObject *, QEvent *event) override;
|
||||
|
||||
private:
|
||||
QTextEdit *m_label;
|
||||
QWidget *m_icon;
|
||||
std::unique_ptr<ItemWidget> m_childItem;
|
||||
};
|
||||
|
||||
class ItemSyncSaver : public QObject, public ItemSaverInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ItemSyncSaver(const QString &tabPath);
|
||||
|
||||
ItemSyncSaver(
|
||||
QAbstractItemModel *model,
|
||||
const QString &tabPath,
|
||||
const QString &path,
|
||||
const QStringList &files,
|
||||
int maxItems,
|
||||
const QList<FileFormat> &formatSettings);
|
||||
|
||||
bool saveItems(const QString &tabName, const QAbstractItemModel &model, QIODevice *file) override;
|
||||
|
||||
bool canRemoveItems(const QList<QModelIndex> &indexList, QString *error) override;
|
||||
|
||||
bool canMoveItems(const QList<QModelIndex> &indexList) override;
|
||||
|
||||
void itemsRemovedByUser(const QList<QModelIndex> &indexList) override;
|
||||
|
||||
QVariantMap copyItem(const QAbstractItemModel &model, const QVariantMap &itemData) override;
|
||||
|
||||
private:
|
||||
QString m_tabPath;
|
||||
FileWatcher *m_watcher;
|
||||
};
|
||||
|
||||
class ItemSyncScriptable : public ItemScriptable
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QVariantMap tabPaths READ getTabPaths)
|
||||
|
||||
public:
|
||||
ItemSyncScriptable(const ItemSyncTabPaths &tabPaths, QObject *parent);
|
||||
|
||||
QVariantMap getTabPaths() const;
|
||||
|
||||
private:
|
||||
QVariantMap m_tabPaths;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronizes selected tab with destination path.
|
||||
*
|
||||
* For all tabs that have user-set synchronization directory, loads up to maximum number of items
|
||||
* from files (tries to use the same files every time tab is loaded).
|
||||
*
|
||||
* Items contains base name of assigned files (MIME is 'application/x-copyq-itemsync-basename').
|
||||
* E.g. files 'example.txt', 'example.html' and 'example_notes.txt' belong to single item with
|
||||
* base name 'example' containing text, HTML and notes.
|
||||
*
|
||||
* If item data are changed it is saved to appropriate files and vice versa.
|
||||
*
|
||||
* If files is in synchronization directory but is of unknown type, hidden, unreadable or its
|
||||
* file name starts with dot (this is hidden file on UNIX so lets make it same everywhere) it
|
||||
* won't be added to the list.
|
||||
*
|
||||
* Unknown file types can be defined in settings so such files are loaded.
|
||||
*
|
||||
* Item data with unknown MIME type is serialized in '<BASE NAME>_copyq.dat' file.
|
||||
*/
|
||||
class ItemSyncLoader : public QObject, public ItemLoaderInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID COPYQ_PLUGIN_ITEM_LOADER_ID)
|
||||
Q_INTERFACES(ItemLoaderInterface)
|
||||
|
||||
public:
|
||||
ItemSyncLoader();
|
||||
~ItemSyncLoader();
|
||||
|
||||
QString id() const override { return "itemsync"; }
|
||||
QString name() const override { return tr("Synchronize"); }
|
||||
QString author() const override { return QString(); }
|
||||
QString description() const override { return tr("Synchronize items and notes with a directory on disk."); }
|
||||
QVariant icon() const override { return QVariant(IconUploadAlt); }
|
||||
|
||||
QVariantMap applySettings() override;
|
||||
|
||||
void loadSettings(const QVariantMap &settings) override;
|
||||
|
||||
QWidget *createSettingsWidget(QWidget *parent) override;
|
||||
|
||||
bool canLoadItems(QIODevice *file) const override;
|
||||
|
||||
bool canSaveItems(const QString &tabName) const override;
|
||||
|
||||
ItemSaverPtr loadItems(const QString &tabName, QAbstractItemModel *model, QIODevice *file, int maxItems) override;
|
||||
|
||||
ItemSaverPtr initializeTab(const QString &tabName, QAbstractItemModel *model, int maxItems) override;
|
||||
|
||||
ItemWidget *transform(ItemWidget *itemWidget, const QModelIndex &index) override;
|
||||
|
||||
bool matches(const QModelIndex &index, const QRegExp &re) const override;
|
||||
|
||||
QObject *tests(const TestInterfacePtr &test) const override;
|
||||
|
||||
const QObject *signaler() const override { return this; }
|
||||
|
||||
ItemScriptable *scriptableObject(QObject *parent) override;
|
||||
|
||||
signals:
|
||||
void error(const QString &);
|
||||
|
||||
private slots:
|
||||
void onBrowseButtonClicked();
|
||||
|
||||
private:
|
||||
ItemSaverPtr loadItems(const QString &tabName, QAbstractItemModel *model, const QStringList &files, int maxItems);
|
||||
|
||||
std::unique_ptr<Ui::ItemSyncSettings> ui;
|
||||
QVariantMap m_settings;
|
||||
ItemSyncTabPaths m_tabPaths;
|
||||
QList<FileFormat> m_formatSettings;
|
||||
};
|
||||
|
||||
#endif // ITEMSYNC_H
|
@ -0,0 +1,30 @@
|
||||
include(../plugins_common.pri)
|
||||
|
||||
HEADERS += \
|
||||
itemsync.h \
|
||||
filewatcher.h \
|
||||
../../src/gui/iconselectbutton.h \
|
||||
../../src/gui/iconselectdialog.h \
|
||||
../../src/gui/iconwidget.h
|
||||
|
||||
SOURCES += \
|
||||
itemsync.cpp \
|
||||
filewatcher.cpp \
|
||||
../../src/common/config.cpp \
|
||||
../../src/common/log.cpp \
|
||||
../../src/common/mimetypes.cpp \
|
||||
../../src/gui/iconfont.cpp \
|
||||
../../src/gui/iconselectbutton.cpp \
|
||||
../../src/gui/iconselectdialog.cpp \
|
||||
../../src/gui/iconwidget.cpp \
|
||||
../../src/item/serialize.cpp
|
||||
|
||||
FORMS += itemsyncsettings.ui
|
||||
|
||||
CONFIG(debug, debug|release) {
|
||||
SOURCES += tests/itemsynctests.cpp
|
||||
HEADERS += tests/itemsynctests.h
|
||||
}
|
||||
|
||||
TARGET = $$qtLibraryTarget(itemsync)
|
||||
|
@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ItemSyncSettings</class>
|
||||
<widget class="QWidget" name="ItemSyncSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>348</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="verticalLayoutWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Synchronization Tabs and Directories</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string><p>Synchronize contents of <strong>tab</strong> with directory with given <strong>path</strong>.</p>
|
||||
<p>Set <strong>empty path</strong> not to save items in <strong>tab</strong>.</p></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="tableWidgetSyncTabs">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="showGrid">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Tab Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Path</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="verticalLayoutWidget_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Files to Item Data Formats</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string><p>Set MIME type to <strong>-</strong> (dash) to ignore files. Any other unknown or hidden files are ignored.</p>
|
||||
<p>Example: Load <strong>txt</strong> file extension as <strong>text/plain</strong> MIME type.</p></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="tableWidgetFormatSettings">
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Extensions</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Item MIME Type</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,546 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemsynctests.h"
|
||||
|
||||
#include "common/mimetypes.h"
|
||||
#include "tests/test_utils.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
using FilePtr = std::shared_ptr<QFile>;
|
||||
|
||||
const char sep[] = " ;; ";
|
||||
|
||||
QString fileNameForId(int i)
|
||||
{
|
||||
return QString("copyq_%1.txt").arg(i, 4, 10, QChar('0'));
|
||||
}
|
||||
|
||||
class TestDir {
|
||||
public:
|
||||
explicit TestDir(int i, bool createPath = true)
|
||||
: m_dir(ItemSyncTests::testDir(i))
|
||||
{
|
||||
clear();
|
||||
if (createPath)
|
||||
create();
|
||||
}
|
||||
|
||||
~TestDir()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
if (isValid()) {
|
||||
for ( const auto &fileName : files() )
|
||||
remove(fileName);
|
||||
m_dir.rmpath(".");
|
||||
}
|
||||
}
|
||||
|
||||
void create()
|
||||
{
|
||||
if ( !m_dir.mkpath(".") )
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return m_dir.exists();
|
||||
}
|
||||
|
||||
QStringList files() const
|
||||
{
|
||||
return m_dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Name);
|
||||
}
|
||||
|
||||
FilePtr file(const QString &fileName) const
|
||||
{
|
||||
return std::make_shared<QFile>(filePath(fileName));
|
||||
}
|
||||
|
||||
QString filePath(const QString &fileName) const
|
||||
{
|
||||
return m_dir.absoluteFilePath(fileName);
|
||||
}
|
||||
|
||||
bool remove(const QString &fileName)
|
||||
{
|
||||
return QFile::remove(filePath(fileName));
|
||||
}
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(TestDir)
|
||||
QDir m_dir;
|
||||
};
|
||||
|
||||
QByteArray createFile(const TestDir &dir, const QString &fileName, const QByteArray &content)
|
||||
{
|
||||
FilePtr file(dir.file(fileName));
|
||||
if ( file->exists() )
|
||||
return "File already exists!";
|
||||
|
||||
if ( !file->open(QIODevice::WriteOnly) )
|
||||
return "Cannot open file!";
|
||||
|
||||
if ( file->write(content) == -1 )
|
||||
return "Cannot write to file!";
|
||||
|
||||
file->close();
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemSyncTests::ItemSyncTests(const TestInterfacePtr &test, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_test(test)
|
||||
{
|
||||
}
|
||||
|
||||
QString ItemSyncTests::testTab(int i)
|
||||
{
|
||||
return ::testTab(i);
|
||||
}
|
||||
|
||||
QString ItemSyncTests::testDir(int i)
|
||||
{
|
||||
return QDir::tempPath() + "/copyq_test_dirs/itemsync_" + QString::number(i);
|
||||
}
|
||||
|
||||
void ItemSyncTests::initTestCase()
|
||||
{
|
||||
TEST(m_test->initTestCase());
|
||||
}
|
||||
|
||||
void ItemSyncTests::cleanupTestCase()
|
||||
{
|
||||
TEST(m_test->cleanupTestCase());
|
||||
}
|
||||
|
||||
void ItemSyncTests::init()
|
||||
{
|
||||
TEST(m_test->init());
|
||||
|
||||
// Remove temporary directory.
|
||||
for (int i = 0; i < 10; ++i)
|
||||
TestDir(i, false);
|
||||
|
||||
QDir tmpDir(QDir::cleanPath(testDir(0) + "/.."));
|
||||
if ( tmpDir.exists() )
|
||||
QVERIFY(tmpDir.rmdir("."));
|
||||
}
|
||||
|
||||
void ItemSyncTests::cleanup()
|
||||
{
|
||||
TEST( m_test->cleanup() );
|
||||
}
|
||||
|
||||
void ItemSyncTests::createRemoveTestDir()
|
||||
{
|
||||
TestDir dir1(1);
|
||||
TestDir dir2(2);
|
||||
|
||||
QVERIFY(dir1.isValid());
|
||||
QCOMPARE(dir1.files().join(sep), QString());
|
||||
|
||||
QVERIFY(dir2.isValid());
|
||||
QCOMPARE(dir2.files().join(sep), QString());
|
||||
|
||||
const QString testFileName1 = "test1.txt";
|
||||
FilePtr f1(dir1.file(testFileName1));
|
||||
QVERIFY(!f1->exists());
|
||||
QVERIFY(f1->open(QIODevice::WriteOnly));
|
||||
f1->close();
|
||||
|
||||
QCOMPARE(dir1.files().join(sep), testFileName1);
|
||||
|
||||
dir1.clear();
|
||||
QVERIFY(!dir1.isValid());
|
||||
QVERIFY(!f1->exists());
|
||||
QVERIFY(dir2.isValid());
|
||||
|
||||
dir2.clear();
|
||||
QVERIFY(!dir1.isValid());
|
||||
QVERIFY(!dir2.isValid());
|
||||
|
||||
dir1.create();
|
||||
QVERIFY(dir1.isValid());
|
||||
QCOMPARE(dir2.files().join(sep), QString());
|
||||
}
|
||||
|
||||
void ItemSyncTests::itemsToFiles()
|
||||
{
|
||||
TestDir dir1(1);
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "tab" << tab1;
|
||||
|
||||
RUN(args << "add" << "A" << "B" << "C", "");
|
||||
RUN(args << "read" << "0" << "1" << "2", "C\nB\nA");
|
||||
RUN(args << "size", "3\n");
|
||||
|
||||
QCOMPARE( dir1.files().join(sep),
|
||||
fileNameForId(0) + sep + fileNameForId(1) + sep + fileNameForId(2) );
|
||||
}
|
||||
|
||||
void ItemSyncTests::filesToItems()
|
||||
{
|
||||
TestDir dir1(1);
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "tab" << tab1;
|
||||
|
||||
RUN(args << "size", "0\n");
|
||||
|
||||
const QByteArray text1 = "Hello world!";
|
||||
createFile(dir1, "test1.txt", text1);
|
||||
|
||||
QTest::qSleep(1200);
|
||||
|
||||
const QByteArray text2 = "And hello again!";
|
||||
TEST(createFile(dir1, "test2.txt", text2));
|
||||
|
||||
WAIT_ON_OUTPUT(args << "size", "2\n");
|
||||
// Newer files first.
|
||||
RUN(args << "read" << "0", text2);
|
||||
RUN(args << "read" << "1", text1);
|
||||
}
|
||||
|
||||
void ItemSyncTests::removeOwnItems()
|
||||
{
|
||||
TestDir dir1(1);
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "separator" << "," << "tab" << tab1;
|
||||
|
||||
RUN(args << "add" << "A" << "B" << "C" << "D", "");
|
||||
|
||||
const QString fileA = fileNameForId(0);
|
||||
const QString fileB = fileNameForId(1);
|
||||
const QString fileC = fileNameForId(2);
|
||||
const QString fileD = fileNameForId(3);
|
||||
|
||||
QCOMPARE( dir1.files().join(sep),
|
||||
fileA
|
||||
+ sep + fileB
|
||||
+ sep + fileC
|
||||
+ sep + fileD
|
||||
);
|
||||
|
||||
// Move to test tab and select second and third item.
|
||||
RUN("setCurrentTab" << tab1, "");
|
||||
RUN(args << "selectItems" << "1" << "2", "true\n");
|
||||
RUN(args << "testSelected", tab1.toUtf8() + " 2 1 2\n");
|
||||
|
||||
// Remove selected items.
|
||||
RUN(args << "keys" << m_test->shortcutToRemove(), "");
|
||||
RUN(args << "read" << "0" << "1" << "2" << "3", "D,A,,");
|
||||
QCOMPARE( dir1.files().join(sep),
|
||||
fileA
|
||||
+ sep + fileD
|
||||
);
|
||||
|
||||
// Removing own items works from script.
|
||||
RUN(args << "remove" << "1", "");
|
||||
RUN(args << "read" << "0" << "1" << "2" << "3", "D,,,");
|
||||
QCOMPARE( dir1.files().join(sep), fileD );
|
||||
}
|
||||
|
||||
void ItemSyncTests::removeNotOwnedItems()
|
||||
{
|
||||
TestDir dir1(1);
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "separator" << "," << "tab" << tab1;
|
||||
|
||||
const QString fileA = "test1.txt";
|
||||
const QString fileB = "test2.txt";
|
||||
const QString fileC = "test3.txt";
|
||||
const QString fileD = "test4.txt";
|
||||
|
||||
TEST(createFile(dir1, fileA, "A"));
|
||||
WAIT_ON_OUTPUT(args << "size", "1\n");
|
||||
TEST(createFile(dir1, fileB, "B"));
|
||||
WAIT_ON_OUTPUT(args << "size", "2\n");
|
||||
TEST(createFile(dir1, fileC, "C"));
|
||||
WAIT_ON_OUTPUT(args << "size", "3\n");
|
||||
TEST(createFile(dir1, fileD, "D"));
|
||||
WAIT_ON_OUTPUT(args << "size", "4\n");
|
||||
|
||||
QCOMPARE( dir1.files().join(sep),
|
||||
fileA
|
||||
+ sep + fileB
|
||||
+ sep + fileC
|
||||
+ sep + fileD
|
||||
);
|
||||
|
||||
// Move to test tab and select second and third item.
|
||||
RUN("setCurrentTab" << tab1, "");
|
||||
RUN(args << "selectItems" << "1" << "2", "true\n");
|
||||
RUN(args << "testSelected", tab1.toUtf8() + " 2 1 2\n");
|
||||
|
||||
// Don't accept the "Remove Items?" dialog.
|
||||
RUN(args << "keys" << m_test->shortcutToRemove(), "");
|
||||
RUN(args << "keys" << "ESCAPE", "");
|
||||
RUN(args << "read" << "0" << "1" << "2" << "3", "D,C,B,A");
|
||||
QCOMPARE( dir1.files().join(sep),
|
||||
fileA
|
||||
+ sep + fileB
|
||||
+ sep + fileC
|
||||
+ sep + fileD
|
||||
);
|
||||
|
||||
// Accept the "Remove Items?" dialog.
|
||||
RUN(args << "keys" << m_test->shortcutToRemove(), "");
|
||||
RUN(args << "keys" << "ENTER", "");
|
||||
RUN(args << "read" << "0" << "1" << "2" << "3", "D,A,,");
|
||||
QCOMPARE( dir1.files().join(sep),
|
||||
fileA
|
||||
+ sep + fileD
|
||||
);
|
||||
|
||||
// Removing not owned items from script doesn't work.
|
||||
RUN_EXPECT_ERROR(args << "remove" << "1", CommandException);
|
||||
QCOMPARE( dir1.files().join(sep),
|
||||
fileA
|
||||
+ sep + fileD
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
void ItemSyncTests::removeFiles()
|
||||
{
|
||||
TestDir dir1(1);
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "separator" << "," << "tab" << tab1;
|
||||
|
||||
RUN(args << "add" << "A" << "B" << "C" << "D", "");
|
||||
|
||||
const QString fileA = fileNameForId(0);
|
||||
const QString fileB = fileNameForId(1);
|
||||
const QString fileC = fileNameForId(2);
|
||||
const QString fileD = fileNameForId(3);
|
||||
|
||||
QCOMPARE( dir1.files().join(sep),
|
||||
fileA
|
||||
+ sep + fileB
|
||||
+ sep + fileC
|
||||
+ sep + fileD
|
||||
);
|
||||
|
||||
FilePtr file = dir1.file(fileC);
|
||||
QVERIFY(file->open(QIODevice::ReadOnly));
|
||||
QCOMPARE(file->readAll().data(), QByteArray("C").data());
|
||||
file->remove();
|
||||
|
||||
WAIT_ON_OUTPUT(args << "size", "3\n");
|
||||
RUN(args << "read" << "0" << "1" << "2", "D,B,A");
|
||||
|
||||
dir1.file(fileB)->remove();
|
||||
dir1.file(fileA)->remove();
|
||||
|
||||
WAIT_ON_OUTPUT(args << "size", "1\n");
|
||||
RUN(args << "read" << "0", "D");
|
||||
}
|
||||
|
||||
void ItemSyncTests::modifyItems()
|
||||
{
|
||||
TestDir dir1(1);
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "separator" << "," << "tab" << tab1;
|
||||
|
||||
RUN(args << "add" << "A" << "B" << "C" << "D", "");
|
||||
|
||||
const QString fileC = fileNameForId(2);
|
||||
FilePtr file = dir1.file(fileC);
|
||||
QVERIFY(file->open(QIODevice::ReadOnly));
|
||||
QCOMPARE(file->readAll().data(), QByteArray("C").data());
|
||||
file->close();
|
||||
|
||||
RUN(args << "keys" << "RIGHT" << "HOME" << "DOWN" << "F2" << ":XXX" << "F2", "");
|
||||
RUN(args << "size", "4\n");
|
||||
RUN(args << "read" << "0" << "1" << "2" << "3", "D,XXX,B,A");
|
||||
|
||||
file = dir1.file(fileC);
|
||||
QVERIFY(file->open(QIODevice::ReadOnly));
|
||||
QCOMPARE(file->readAll().data(), QByteArray("XXX").data());
|
||||
file->close();
|
||||
}
|
||||
|
||||
void ItemSyncTests::modifyFiles()
|
||||
{
|
||||
TestDir dir1(1);
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "separator" << "," << "tab" << tab1;
|
||||
|
||||
RUN(args << "add" << "A" << "B" << "C" << "D", "");
|
||||
|
||||
const QString fileA = fileNameForId(0);
|
||||
const QString fileB = fileNameForId(1);
|
||||
const QString fileC = fileNameForId(2);
|
||||
const QString fileD = fileNameForId(3);
|
||||
|
||||
QCOMPARE( dir1.files().join(sep),
|
||||
fileA
|
||||
+ sep + fileB
|
||||
+ sep + fileC
|
||||
+ sep + fileD
|
||||
);
|
||||
|
||||
FilePtr file = dir1.file(fileC);
|
||||
QVERIFY(file->open(QIODevice::ReadWrite));
|
||||
QCOMPARE(file->readAll().data(), QByteArray("C").data());
|
||||
file->write("X");
|
||||
file->close();
|
||||
|
||||
WAIT_ON_OUTPUT(args << "read" << "0" << "1" << "2" << "3", "D,CX,B,A");
|
||||
RUN(args << "size", "4\n");
|
||||
}
|
||||
|
||||
void ItemSyncTests::notes()
|
||||
{
|
||||
TestDir dir1(1);
|
||||
const QString tab1 = testTab(1);
|
||||
|
||||
const Args args = Args() << "separator" << ";" << "tab" << tab1;
|
||||
|
||||
RUN(args << "add" << "TEST1", "");
|
||||
|
||||
RUN(args << "keys" << "LEFT"
|
||||
<< "CTRL+N" << ":TEST2" << "F2"
|
||||
<< "CTRL+N" << ":TEST3" << "F2", "");
|
||||
RUN(args << "size", "3\n");
|
||||
RUN(args << "read" << "0" << "1" << "2", "TEST3;TEST2;TEST1");
|
||||
|
||||
const QString fileTest1 = fileNameForId(0);
|
||||
const QString fileTest2 = fileNameForId(1);
|
||||
const QString fileTest3 = fileNameForId(2);
|
||||
|
||||
const QStringList files1 = QStringList() << fileTest1 << fileTest2 << fileTest3;
|
||||
|
||||
QCOMPARE( dir1.files().join(sep), files1.join(sep) );
|
||||
|
||||
RUN(args << "keys" << "HOME" << "DOWN" << "SHIFT+F2" << ":NOTE1" << "F2", "");
|
||||
RUN(args << "read" << mimeItemNotes << "0" << "1" << "2", ";NOTE1;");
|
||||
|
||||
// One new file for notes.
|
||||
const QStringList files2 = dir1.files();
|
||||
const QSet<QString> filesDiff = files2.toSet() - files1.toSet();
|
||||
QCOMPARE( filesDiff.size(), 1 );
|
||||
const QString fileNote = *filesDiff.begin();
|
||||
|
||||
// Read file with the notes.
|
||||
FilePtr file = dir1.file(fileNote);
|
||||
QVERIFY(file->open(QIODevice::ReadWrite));
|
||||
QCOMPARE(file->readAll().data(), QByteArray("NOTE1").data());
|
||||
|
||||
// Modify notes.
|
||||
file->write("+NOTE2");
|
||||
file->close();
|
||||
|
||||
WAIT_ON_OUTPUT(args << "read" << mimeItemNotes << "0" << "1" << "2", ";NOTE1+NOTE2;");
|
||||
RUN(args << "size", "3\n");
|
||||
|
||||
// Remove notes.
|
||||
QVERIFY(file->remove());
|
||||
|
||||
WAIT_ON_OUTPUT(args << "read" << mimeItemNotes << "0" << "1" << "2", ";;");
|
||||
RUN(args << "size", "3\n");
|
||||
}
|
||||
|
||||
void ItemSyncTests::customFormats()
|
||||
{
|
||||
TestDir dir1(1);
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "separator" << ";" << "tab" << tab1;
|
||||
|
||||
const QByteArray data1 = "Custom format content";
|
||||
createFile(dir1, "test1.xxx", data1);
|
||||
|
||||
WAIT_ON_OUTPUT(args << "size", "1\n");
|
||||
RUN(args << "keys" << "LEFT", "");
|
||||
RUN(args << "read" << COPYQ_MIME_PREFIX "test-xxx" << "0", data1);
|
||||
|
||||
const QByteArray data2 = "Other custom format content";
|
||||
createFile(dir1, "test2.yyy", data2);
|
||||
|
||||
WAIT_ON_OUTPUT(args << "size", "2\n");
|
||||
RUN(args << "read" << COPYQ_MIME_PREFIX "test-zzz" << "0", data2);
|
||||
|
||||
const QByteArray data3 = "Last custom format content";
|
||||
createFile(dir1, "test3.zzz", data3);
|
||||
|
||||
WAIT_ON_OUTPUT(args << "size", "3\n");
|
||||
RUN(args << "read" << COPYQ_MIME_PREFIX "test-zzz" << "0", data3);
|
||||
|
||||
RUN(args << "read" << COPYQ_MIME_PREFIX "test-xxx" << "0" << "1" << "2",
|
||||
";;" + data1);
|
||||
RUN(args << "read" << COPYQ_MIME_PREFIX "test-zzz" << "0" << "1" << "2",
|
||||
data3 + ";" + data2 + ";");
|
||||
RUN(args << "read" << COPYQ_MIME_PREFIX "test-zzz" << "0" << "1" << COPYQ_MIME_PREFIX "test-xxx" << "2",
|
||||
data3 + ";" + data2 + ";" + data1);
|
||||
|
||||
// Remove
|
||||
dir1.remove("test2.yyy");
|
||||
|
||||
WAIT_ON_OUTPUT(args << "size", "2\n");
|
||||
RUN(args << "read" << COPYQ_MIME_PREFIX "test-zzz" << "0" << COPYQ_MIME_PREFIX "test-xxx" << "1",
|
||||
data3 + ";" + data1);
|
||||
|
||||
// Modify file
|
||||
const QByteArray data4 = " with update!";
|
||||
FilePtr file = dir1.file("test1.xxx");
|
||||
QVERIFY(file->open(QIODevice::Append));
|
||||
file->write(data4);
|
||||
file->close();
|
||||
|
||||
WAIT_ON_OUTPUT(
|
||||
Args(args) << "read" << COPYQ_MIME_PREFIX "test-zzz" << "0" << COPYQ_MIME_PREFIX "test-xxx" << "1",
|
||||
data3 + ";" + data1 + data4);
|
||||
RUN(args << "size", "2\n");
|
||||
|
||||
// Create item with custom data
|
||||
const QByteArray data5 = "New item data!";
|
||||
RUN(args << "write" << COPYQ_MIME_PREFIX "test-zzz" << data5, "");
|
||||
|
||||
RUN(args << "size", "3\n");
|
||||
|
||||
const QString fileData = QString(fileNameForId(0)).replace("txt", "zzz");
|
||||
|
||||
// Check data
|
||||
const QByteArray data6 = " And another data!";
|
||||
file = dir1.file(fileData);
|
||||
QVERIFY(file->exists());
|
||||
QVERIFY(file->open(QIODevice::ReadWrite));
|
||||
QCOMPARE(file->readAll().data(), data5.data());
|
||||
|
||||
// Modify data
|
||||
file->write(data6);
|
||||
file->close();
|
||||
|
||||
WAIT_ON_OUTPUT(
|
||||
Args(args) << "read" << COPYQ_MIME_PREFIX "test-zzz" << "0" << "1" << COPYQ_MIME_PREFIX "test-xxx" << "2",
|
||||
data5 + data6 + ";" + data3 + ";" + data1 + data4);
|
||||
RUN(args << "size", "3\n");
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMSYNCTESTS_H
|
||||
#define ITEMSYNCTESTS_H
|
||||
|
||||
#include "tests/testinterface.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class ItemSyncTests : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ItemSyncTests(const TestInterfacePtr &test, QObject *parent = nullptr);
|
||||
|
||||
static QString testTab(int i);
|
||||
|
||||
static QString testDir(int i);
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
void createRemoveTestDir();
|
||||
|
||||
void itemsToFiles();
|
||||
void filesToItems();
|
||||
|
||||
void removeOwnItems();
|
||||
void removeNotOwnedItems();
|
||||
void removeFiles();
|
||||
|
||||
void modifyItems();
|
||||
void modifyFiles();
|
||||
|
||||
void notes();
|
||||
|
||||
void customFormats();
|
||||
|
||||
private:
|
||||
TestInterfacePtr m_test;
|
||||
};
|
||||
|
||||
#endif // ITEMSYNCTESTS_H
|
@ -0,0 +1,12 @@
|
||||
set(copyq_plugin_itemtags_SOURCES
|
||||
../../src/common/config.cpp
|
||||
../../src/common/mimetypes.cpp
|
||||
../../src/common/textdata.cpp
|
||||
../../src/gui/iconselectbutton.cpp
|
||||
../../src/gui/iconselectdialog.cpp
|
||||
../../src/gui/iconwidget.cpp
|
||||
../../src/gui/iconfont.cpp
|
||||
)
|
||||
|
||||
copyq_add_plugin(itemtags)
|
||||
|
@ -0,0 +1,910 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemtags.h"
|
||||
#include "ui_itemtagssettings.h"
|
||||
|
||||
#include "common/command.h"
|
||||
#include "common/contenttype.h"
|
||||
#include "common/textdata.h"
|
||||
#include "gui/iconfont.h"
|
||||
#include "gui/iconselectbutton.h"
|
||||
|
||||
#ifdef HAS_TESTS
|
||||
# include "tests/itemtagstests.h"
|
||||
#endif
|
||||
|
||||
#include <QBoxLayout>
|
||||
#include <QColorDialog>
|
||||
#include <QLabel>
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QPushButton>
|
||||
#include <QSettings>
|
||||
#include <QtPlugin>
|
||||
#include <QUrl>
|
||||
|
||||
Q_DECLARE_METATYPE(ItemTags::Tag)
|
||||
|
||||
namespace {
|
||||
|
||||
const char mimeTags[] = "application/x-copyq-tags";
|
||||
|
||||
const char configTags[] = "tags";
|
||||
|
||||
const char propertyColor[] = "CopyQ_color";
|
||||
|
||||
namespace tagsTableColumns {
|
||||
enum {
|
||||
name,
|
||||
match,
|
||||
styleSheet,
|
||||
color,
|
||||
icon
|
||||
};
|
||||
}
|
||||
|
||||
class ElidedLabel : public QLabel
|
||||
{
|
||||
public:
|
||||
explicit ElidedLabel(const QString &text, QWidget *parent = nullptr)
|
||||
: QLabel(text, parent)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override
|
||||
{
|
||||
QPainter p(this);
|
||||
QFontMetrics fm = fontMetrics();
|
||||
const QString elidedText = fm.elidedText(text(), Qt::ElideMiddle, rect().width());
|
||||
p.drawText(rect(), Qt::AlignCenter, elidedText);
|
||||
}
|
||||
};
|
||||
|
||||
bool isTagValid(const ItemTags::Tag &tag)
|
||||
{
|
||||
return !tag.name.isEmpty()
|
||||
|| !tag.icon.isEmpty()
|
||||
|| !tag.styleSheet.isEmpty()
|
||||
|| !tag.match.isEmpty();
|
||||
}
|
||||
|
||||
QString serializeColor(const QColor &color)
|
||||
{
|
||||
if (color.alpha() == 255)
|
||||
return color.name();
|
||||
|
||||
return QString("rgba(%1,%2,%3,%4)")
|
||||
.arg(color.red())
|
||||
.arg(color.green())
|
||||
.arg(color.blue())
|
||||
.arg(color.alpha());
|
||||
}
|
||||
|
||||
QColor deserializeColor(const QString &colorName)
|
||||
{
|
||||
if ( colorName.startsWith("rgba(") ) {
|
||||
QStringList list = colorName.mid(5, colorName.indexOf(')') - 5).split(',');
|
||||
int r = list.value(0).toInt();
|
||||
int g = list.value(1).toInt();
|
||||
int b = list.value(2).toInt();
|
||||
int a = list.value(3).toInt();
|
||||
|
||||
return QColor(r, g, b, a);
|
||||
}
|
||||
|
||||
return QColor(colorName);
|
||||
}
|
||||
|
||||
void setColorIcon(QPushButton *button, const QColor &color)
|
||||
{
|
||||
QPixmap pix(button->iconSize());
|
||||
pix.fill(color);
|
||||
button->setIcon(pix);
|
||||
button->setProperty(propertyColor, color);
|
||||
}
|
||||
|
||||
void setHeaderSectionResizeMode(QTableWidget *table, int logicalIndex, QHeaderView::ResizeMode mode)
|
||||
{
|
||||
#if QT_VERSION < 0x050000
|
||||
table->horizontalHeader()->setResizeMode(logicalIndex, mode);
|
||||
#else
|
||||
table->horizontalHeader()->setSectionResizeMode(logicalIndex, mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
void setFixedColumnSize(QTableWidget *table, int logicalIndex)
|
||||
{
|
||||
setHeaderSectionResizeMode(table, logicalIndex, QHeaderView::Fixed);
|
||||
table->horizontalHeader()->resizeSection(logicalIndex, table->rowHeight(0));
|
||||
}
|
||||
|
||||
QVariant cellWidgetProperty(QTableWidget *table, int row, int column, const char *property)
|
||||
{
|
||||
return table->cellWidget(row, column)->property(property);
|
||||
}
|
||||
|
||||
QString tags(const QModelIndex &index)
|
||||
{
|
||||
const QByteArray tagsData =
|
||||
index.data(contentType::data).toMap().value(mimeTags).toByteArray();
|
||||
return getTextData(tagsData);
|
||||
}
|
||||
|
||||
QString toScriptString(const QString &text)
|
||||
{
|
||||
return "decodeURIComponent('" + QUrl::toPercentEncoding(text) + "')";
|
||||
}
|
||||
|
||||
QString addTagText()
|
||||
{
|
||||
return ItemTagsLoader::tr("Add a Tag");
|
||||
}
|
||||
|
||||
QString removeTagText()
|
||||
{
|
||||
return ItemTagsLoader::tr("Remove a Tag");
|
||||
}
|
||||
|
||||
Command dummyTagCommand()
|
||||
{
|
||||
Command c;
|
||||
c.icon = QString(QChar(IconTag));
|
||||
c.inMenu = true;
|
||||
return c;
|
||||
}
|
||||
|
||||
void addTagCommands(const QString &tagName, const QString &match, QList<Command> *commands)
|
||||
{
|
||||
Command c;
|
||||
|
||||
const QString name = !tagName.isEmpty() ? tagName : match;
|
||||
const QString tagString = toScriptString(name);
|
||||
|
||||
c = dummyTagCommand();
|
||||
c.name = ItemTagsLoader::tr("Tag as %1").arg(quoteString(name));
|
||||
c.matchCmd = "copyq: plugins.itemtags.hasTag(" + tagString + ") && fail()";
|
||||
c.cmd = "copyq: plugins.itemtags.tag(" + tagString + ")";
|
||||
commands->append(c);
|
||||
|
||||
c = dummyTagCommand();
|
||||
c.name = ItemTagsLoader::tr("Remove tag %1").arg(quoteString(name));
|
||||
c.matchCmd = "copyq: plugins.itemtags.hasTag(" + tagString + ") || fail()";
|
||||
c.cmd = "copyq: plugins.itemtags.untag(" + tagString + ")";
|
||||
commands->append(c);
|
||||
}
|
||||
|
||||
QString escapeTagField(const QString &field)
|
||||
{
|
||||
return QString(field).replace("\\", "\\\\").replace(";;", ";\\;");
|
||||
}
|
||||
|
||||
QString unescapeTagField(const QString &field)
|
||||
{
|
||||
return QString(field).replace(";\\;", ";;").replace("\\\\", "\\");
|
||||
}
|
||||
|
||||
void initTagWidget(QWidget *tagWidget, const ItemTags::Tag &tag, const QFont &font)
|
||||
{
|
||||
tagWidget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
tagWidget->setStyleSheet(
|
||||
"* {"
|
||||
";background:transparent"
|
||||
";color:" + serializeColor(tag.color) +
|
||||
";" + tag.styleSheet +
|
||||
"}"
|
||||
"QLabel {"
|
||||
";background:transparent"
|
||||
";border:none"
|
||||
"}"
|
||||
);
|
||||
|
||||
auto layout = new QHBoxLayout(tagWidget);
|
||||
const int x = QFontMetrics(font).height() / 6;
|
||||
layout->setContentsMargins(x, x, x, x);
|
||||
layout->setSpacing(x * 2);
|
||||
|
||||
if (tag.icon.size() > 1) {
|
||||
QLabel *iconLabel = new QLabel(tagWidget);
|
||||
const QPixmap icon(tag.icon);
|
||||
iconLabel->setPixmap(icon);
|
||||
layout->addWidget(iconLabel);
|
||||
} else if (tag.icon.size() == 1) {
|
||||
QLabel *iconLabel = new QLabel(tagWidget);
|
||||
iconLabel->setFont(iconFont());
|
||||
iconLabel->setText(tag.icon);
|
||||
layout->addWidget(iconLabel);
|
||||
}
|
||||
|
||||
if (!tag.name.isEmpty()) {
|
||||
auto label = new ElidedLabel(tag.name, tagWidget);
|
||||
label->setFont(font);
|
||||
layout->addWidget(label);
|
||||
}
|
||||
}
|
||||
|
||||
QFont smallerFont(QFont font)
|
||||
{
|
||||
if (font.pixelSize() != -1)
|
||||
font.setPixelSize( static_cast<int>(0.75 * font.pixelSize()) );
|
||||
else
|
||||
font.setPointSizeF(0.75 * font.pointSizeF());
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
void addTagButtons(QBoxLayout *layout, const ItemTags::Tags &tags)
|
||||
{
|
||||
Q_ASSERT(layout->parentWidget());
|
||||
|
||||
layout->addStretch(1);
|
||||
|
||||
const QFont font = smallerFont(layout->parentWidget()->font());
|
||||
|
||||
for (const auto &tag : tags) {
|
||||
QWidget *tagWidget = new QWidget(layout->parentWidget());
|
||||
initTagWidget(tagWidget, tag, font);
|
||||
layout->addWidget(tagWidget);
|
||||
}
|
||||
}
|
||||
|
||||
ItemTags::Tag findMatchingTag(const QString &tagText, const ItemTags::Tags &tags)
|
||||
{
|
||||
for (const auto &tag : tags) {
|
||||
if ( tag.match.isEmpty() ) {
|
||||
if (tag.name == tagText)
|
||||
return tag;
|
||||
} else {
|
||||
const QRegExp re(tag.match);
|
||||
if ( re.exactMatch(tagText) )
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
||||
return ItemTags::Tag();
|
||||
}
|
||||
|
||||
class TagTableWidgetItem : public QTableWidgetItem
|
||||
{
|
||||
public:
|
||||
enum {
|
||||
TagRole = Qt::UserRole
|
||||
};
|
||||
|
||||
explicit TagTableWidgetItem(const QString &text)
|
||||
: QTableWidgetItem(text)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant data(int role) const override
|
||||
{
|
||||
if (role == Qt::DecorationRole)
|
||||
return m_pixmap;
|
||||
|
||||
return QTableWidgetItem::data(role);
|
||||
}
|
||||
|
||||
void setData(int role, const QVariant &value) override
|
||||
{
|
||||
if (role == TagRole)
|
||||
setTag( value.value<ItemTags::Tag>() );
|
||||
|
||||
QTableWidgetItem::setData(role, value);
|
||||
}
|
||||
|
||||
private:
|
||||
void setTag(const ItemTags::Tag &tag)
|
||||
{
|
||||
if ( isTagValid(tag) ) {
|
||||
QWidget tagWidget;
|
||||
initTagWidget(&tagWidget, tag, smallerFont(QFont()));
|
||||
m_pixmap = QPixmap(tagWidget.sizeHint());
|
||||
m_pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&m_pixmap);
|
||||
tagWidget.render(&painter);
|
||||
} else {
|
||||
m_pixmap = QPixmap();
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap m_pixmap;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemTags::ItemTags(ItemWidget *childItem, const Tags &tags)
|
||||
: QWidget( childItem->widget()->parentWidget() )
|
||||
, ItemWidget(this)
|
||||
, m_tagWidget(new QWidget(childItem->widget()->parentWidget()))
|
||||
, m_childItem(childItem)
|
||||
{
|
||||
QBoxLayout *tagLayout = new QHBoxLayout(m_tagWidget);
|
||||
tagLayout->setMargin(0);
|
||||
addTagButtons(tagLayout, tags);
|
||||
|
||||
m_childItem->widget()->setObjectName("item_child");
|
||||
m_childItem->widget()->setParent(this);
|
||||
|
||||
QBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
layout->addWidget(m_tagWidget);
|
||||
layout->addWidget( m_childItem->widget() );
|
||||
}
|
||||
|
||||
void ItemTags::setCurrent(bool current)
|
||||
{
|
||||
m_childItem->setCurrent(current);
|
||||
}
|
||||
|
||||
void ItemTags::highlight(const QRegExp &re, const QFont &highlightFont, const QPalette &highlightPalette)
|
||||
{
|
||||
m_childItem->setHighlight(re, highlightFont, highlightPalette);
|
||||
}
|
||||
|
||||
QWidget *ItemTags::createEditor(QWidget *parent) const
|
||||
{
|
||||
return m_childItem->createEditor(parent);
|
||||
}
|
||||
|
||||
void ItemTags::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
return m_childItem->setEditorData(editor, index);
|
||||
}
|
||||
|
||||
void ItemTags::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
|
||||
{
|
||||
return m_childItem->setModelData(editor, model, index);
|
||||
}
|
||||
|
||||
bool ItemTags::hasChanges(QWidget *editor) const
|
||||
{
|
||||
return m_childItem->hasChanges(editor);
|
||||
}
|
||||
|
||||
QObject *ItemTags::createExternalEditor(const QModelIndex &index, QWidget *parent) const
|
||||
{
|
||||
return m_childItem->createExternalEditor(index, parent);
|
||||
}
|
||||
|
||||
void ItemTags::updateSize(const QSize &maximumSize, int idealWidth)
|
||||
{
|
||||
setMaximumSize(maximumSize);
|
||||
|
||||
m_tagWidget->setFixedWidth(idealWidth);
|
||||
|
||||
m_childItem->updateSize(maximumSize, idealWidth);
|
||||
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
ItemTagsScriptable::ItemTagsScriptable(const QStringList &userTags, QObject *parent)
|
||||
: ItemScriptable(parent)
|
||||
, m_userTags(userTags)
|
||||
{
|
||||
}
|
||||
|
||||
QStringList ItemTagsScriptable::getUserTags() const
|
||||
{
|
||||
return m_userTags;
|
||||
}
|
||||
|
||||
QStringList ItemTagsScriptable::tags()
|
||||
{
|
||||
const auto args = currentArguments();
|
||||
const auto rows = this->rows(args, 0);
|
||||
|
||||
QStringList allTags;
|
||||
for (int row : rows)
|
||||
allTags << this->tags(row);
|
||||
|
||||
return allTags;
|
||||
}
|
||||
|
||||
void ItemTagsScriptable::tag()
|
||||
{
|
||||
const auto args = currentArguments();
|
||||
|
||||
auto tagName = args.value(0).toString();
|
||||
if ( tagName.isEmpty() ) {
|
||||
tagName = askTagName( addTagText(), m_userTags );
|
||||
if ( tagName.isEmpty() )
|
||||
return;
|
||||
}
|
||||
|
||||
if ( args.size() <= 1 ) {
|
||||
const auto dataValueList = call("selectedItemsData").toList();
|
||||
|
||||
QVariantList dataList;
|
||||
for (const auto &itemDataValue : dataValueList) {
|
||||
auto itemData = itemDataValue.toMap();
|
||||
auto itemTags = tags(itemData);
|
||||
if ( addTag(tagName, &itemTags) )
|
||||
itemData.insert( mimeTags, itemTags.join(",") );
|
||||
dataList.append(itemData);
|
||||
}
|
||||
|
||||
call( "setSelectedItemsData", QVariantList() << QVariant(dataList) );
|
||||
} else {
|
||||
for ( int row : rows(args, 1) ) {
|
||||
auto itemTags = tags(row);
|
||||
if ( addTag(tagName, &itemTags) )
|
||||
setTags(row, itemTags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ItemTagsScriptable::untag()
|
||||
{
|
||||
const auto args = currentArguments();
|
||||
auto tagName = args.value(0).toString();
|
||||
|
||||
if ( args.size() <= 1 ) {
|
||||
const auto dataValueList = call("selectedItemsData").toList();
|
||||
|
||||
if ( tagName.isEmpty() ) {
|
||||
QStringList allTags;
|
||||
for (const auto &itemDataValue : dataValueList) {
|
||||
const auto itemData = itemDataValue.toMap();
|
||||
allTags.append( tags(itemData) );
|
||||
}
|
||||
|
||||
tagName = askRemoveTagName(allTags);
|
||||
if ( allTags.isEmpty() )
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantList dataList;
|
||||
for (const auto &itemDataValue : dataValueList) {
|
||||
auto itemData = itemDataValue.toMap();
|
||||
auto itemTags = tags(itemData);
|
||||
if ( removeTag(tagName, &itemTags) )
|
||||
itemData.insert( mimeTags, itemTags.join(",") );
|
||||
dataList.append(itemData);
|
||||
}
|
||||
|
||||
call( "setSelectedItemsData", QVariantList() << QVariant(dataList) );
|
||||
} else {
|
||||
const auto rows = this->rows(args, 1);
|
||||
|
||||
if ( tagName.isEmpty() ) {
|
||||
QStringList allTags;
|
||||
for (int row : rows)
|
||||
allTags.append( this->tags(row) );
|
||||
|
||||
tagName = askRemoveTagName(allTags);
|
||||
if ( allTags.isEmpty() )
|
||||
return;
|
||||
}
|
||||
|
||||
for (int row : rows) {
|
||||
auto itemTags = tags(row);
|
||||
if ( removeTag(tagName, &itemTags) )
|
||||
setTags(row, itemTags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ItemTagsScriptable::clearTags()
|
||||
{
|
||||
const auto args = currentArguments();
|
||||
|
||||
if ( args.isEmpty() ) {
|
||||
const auto dataValueList = call("selectedItemsData").toList();
|
||||
|
||||
QVariantList dataList;
|
||||
for (const auto &itemDataValue : dataValueList) {
|
||||
auto itemData = itemDataValue.toMap();
|
||||
itemData.remove(mimeTags);
|
||||
dataList.append(itemData);
|
||||
}
|
||||
|
||||
call( "setSelectedItemsData", QVariantList() << QVariant(dataList) );
|
||||
} else {
|
||||
const auto rows = this->rows(args, 0);
|
||||
for (int row : rows)
|
||||
setTags(row, QStringList());
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemTagsScriptable::hasTag()
|
||||
{
|
||||
const auto args = currentArguments();
|
||||
const auto tagName = args.value(0).toString();
|
||||
const auto row = args.value(1).toInt();
|
||||
return tags(row).contains(tagName);
|
||||
}
|
||||
|
||||
QString ItemTagsScriptable::askTagName(const QString &dialogTitle, const QStringList &tags)
|
||||
{
|
||||
const auto value = call( "dialog", QVariantList()
|
||||
<< ".title" << dialogTitle
|
||||
<< dialogTitle << tags );
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
QString ItemTagsScriptable::askRemoveTagName(const QStringList &tags)
|
||||
{
|
||||
if ( tags.isEmpty() )
|
||||
return QString();
|
||||
|
||||
if ( tags.size() == 1 )
|
||||
return tags.first();
|
||||
|
||||
return askTagName( removeTagText(), tags );
|
||||
}
|
||||
|
||||
QList<int> ItemTagsScriptable::rows(const QVariantList &arguments, int skip)
|
||||
{
|
||||
QList<int> rows;
|
||||
|
||||
for (int i = skip; i < arguments.size(); ++i) {
|
||||
bool ok;
|
||||
const auto row = arguments[i].toInt(&ok);
|
||||
if (ok)
|
||||
rows.append(row);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
QStringList ItemTagsScriptable::tags(int row)
|
||||
{
|
||||
const auto value = call("read", QVariantList() << mimeTags << row);
|
||||
return tags(value);
|
||||
}
|
||||
|
||||
QStringList ItemTagsScriptable::tags(const QVariant &tags)
|
||||
{
|
||||
return getTextData( tags.toByteArray() )
|
||||
.split(',', QString::SkipEmptyParts);
|
||||
}
|
||||
|
||||
QStringList ItemTagsScriptable::tags(const QVariantMap &itemData)
|
||||
{
|
||||
return tags( itemData.value(mimeTags) );
|
||||
}
|
||||
|
||||
void ItemTagsScriptable::setTags(int row, const QStringList &tags)
|
||||
{
|
||||
const auto value = tags.join(",");
|
||||
call("change", QVariantList() << row << mimeTags << value);
|
||||
}
|
||||
|
||||
bool ItemTagsScriptable::addTag(const QString &tagName, QStringList *tags)
|
||||
{
|
||||
if ( tags->contains(tagName) )
|
||||
return false;
|
||||
|
||||
tags->append(tagName);
|
||||
tags->sort();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ItemTagsScriptable::removeTag(const QString &tagName, QStringList *tags)
|
||||
{
|
||||
if ( !tags->contains(tagName) )
|
||||
return false;
|
||||
|
||||
tags->removeOne(tagName);
|
||||
return true;
|
||||
}
|
||||
|
||||
ItemTagsLoader::ItemTagsLoader()
|
||||
: m_blockDataChange(false)
|
||||
{
|
||||
}
|
||||
|
||||
ItemTagsLoader::~ItemTagsLoader() = default;
|
||||
|
||||
QStringList ItemTagsLoader::formatsToSave() const
|
||||
{
|
||||
return QStringList(mimeTags);
|
||||
}
|
||||
|
||||
QVariantMap ItemTagsLoader::applySettings()
|
||||
{
|
||||
m_tags.clear();
|
||||
|
||||
QStringList tags;
|
||||
|
||||
for (int row = 0; row < ui->tableWidget->rowCount(); ++row) {
|
||||
const Tag tag = tagFromTable(row);
|
||||
if (isTagValid(tag)) {
|
||||
tags.append(serializeTag(tag));
|
||||
m_tags.append(tag);
|
||||
}
|
||||
}
|
||||
|
||||
m_settings.insert(configTags, tags);
|
||||
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
void ItemTagsLoader::loadSettings(const QVariantMap &settings)
|
||||
{
|
||||
m_settings = settings;
|
||||
|
||||
m_tags.clear();
|
||||
for (const auto &tagField : m_settings.value(configTags).toStringList()) {
|
||||
Tag tag = deserializeTag(tagField);
|
||||
if (isTagValid(tag))
|
||||
m_tags.append(tag);
|
||||
}
|
||||
}
|
||||
|
||||
QWidget *ItemTagsLoader::createSettingsWidget(QWidget *parent)
|
||||
{
|
||||
ui.reset(new Ui::ItemTagsSettings);
|
||||
QWidget *w = new QWidget(parent);
|
||||
ui->setupUi(w);
|
||||
|
||||
connect( ui->pushButtonAddCommands, SIGNAL(clicked()),
|
||||
this, SLOT(addCommands()) );
|
||||
|
||||
// Init tag table.
|
||||
for (const auto &tag : m_tags)
|
||||
addTagToSettingsTable(tag);
|
||||
for (int i = 0; i < 10; ++i)
|
||||
addTagToSettingsTable();
|
||||
|
||||
QTableWidget *t = ui->tableWidget;
|
||||
setHeaderSectionResizeMode(t, tagsTableColumns::name, QHeaderView::Stretch);
|
||||
setHeaderSectionResizeMode(t, tagsTableColumns::styleSheet, QHeaderView::Stretch);
|
||||
setHeaderSectionResizeMode(t, tagsTableColumns::match, QHeaderView::Stretch);
|
||||
setFixedColumnSize(t, tagsTableColumns::color);
|
||||
setFixedColumnSize(t, tagsTableColumns::icon);
|
||||
|
||||
connect( ui->tableWidget, SIGNAL(itemChanged(QTableWidgetItem*)),
|
||||
this, SLOT(onTableWidgetItemChanged(QTableWidgetItem*)) );
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
ItemWidget *ItemTagsLoader::transform(ItemWidget *itemWidget, const QModelIndex &index)
|
||||
{
|
||||
const QString tagsContent = tags(index);
|
||||
const Tags tags = toTags(tagsContent);
|
||||
if ( tags.isEmpty() )
|
||||
return nullptr;
|
||||
|
||||
itemWidget->setTagged(true);
|
||||
return new ItemTags(itemWidget, tags);
|
||||
}
|
||||
|
||||
bool ItemTagsLoader::matches(const QModelIndex &index, const QRegExp &re) const
|
||||
{
|
||||
return re.indexIn(tags(index)) != -1;
|
||||
}
|
||||
|
||||
QObject *ItemTagsLoader::tests(const TestInterfacePtr &test) const
|
||||
{
|
||||
#ifdef HAS_TESTS
|
||||
QStringList tags;
|
||||
|
||||
for (const auto &tagName : ItemTagsTests::testTags()) {
|
||||
Tag tag;
|
||||
tag.name = tagName;
|
||||
tags.append(serializeTag(tag));
|
||||
}
|
||||
|
||||
QVariantMap settings;
|
||||
settings[configTags] = tags;
|
||||
|
||||
QObject *tests = new ItemTagsTests(test);
|
||||
tests->setProperty("CopyQ_test_settings", settings);
|
||||
return tests;
|
||||
#else
|
||||
Q_UNUSED(test);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
ItemScriptable *ItemTagsLoader::scriptableObject(QObject *parent)
|
||||
{
|
||||
return new ItemTagsScriptable(userTags(), parent);
|
||||
}
|
||||
|
||||
QList<Command> ItemTagsLoader::commands() const
|
||||
{
|
||||
QList<Command> commands;
|
||||
|
||||
if (m_tags.isEmpty()) {
|
||||
addTagCommands(tr("Important", "Tag name for example command"), QString(), &commands);
|
||||
} else {
|
||||
for (const auto &tag : m_tags)
|
||||
addTagCommands(tag.name, tag.match, &commands);
|
||||
}
|
||||
|
||||
Command c;
|
||||
|
||||
c = dummyTagCommand();
|
||||
c.name = addTagText();
|
||||
c.cmd = "copyq: plugins.itemtags.tag()";
|
||||
commands.append(c);
|
||||
|
||||
c = dummyTagCommand();
|
||||
c.input = mimeTags;
|
||||
c.name = removeTagText();
|
||||
c.cmd = "copyq: plugins.itemtags.untag()";
|
||||
commands.append(c);
|
||||
|
||||
c = dummyTagCommand();
|
||||
c.input = mimeTags;
|
||||
c.name = tr("Clear all tags");
|
||||
c.cmd = "copyq: plugins.itemtags.clearTags()";
|
||||
commands.append(c);
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
QStringList ItemTagsLoader::userTags() const
|
||||
{
|
||||
QStringList tags;
|
||||
|
||||
for (const auto &tag : m_tags)
|
||||
tags.append(tag.name);
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
void ItemTagsLoader::addCommands()
|
||||
{
|
||||
emit addCommands(commands());
|
||||
}
|
||||
|
||||
void ItemTagsLoader::onColorButtonClicked()
|
||||
{
|
||||
QPushButton *button = qobject_cast<QPushButton*>(sender());
|
||||
Q_ASSERT(button);
|
||||
|
||||
const QColor color = button->property(propertyColor).value<QColor>();
|
||||
QColorDialog dialog(button->window());
|
||||
dialog.setOptions(dialog.options() | QColorDialog::ShowAlphaChannel);
|
||||
dialog.setCurrentColor(color);
|
||||
|
||||
if ( dialog.exec() == QDialog::Accepted )
|
||||
setColorIcon( button, dialog.selectedColor() );
|
||||
|
||||
onTableWidgetItemChanged();
|
||||
}
|
||||
|
||||
void ItemTagsLoader::onTableWidgetItemChanged(QTableWidgetItem *item)
|
||||
{
|
||||
// Omit calling this recursively.
|
||||
if (m_blockDataChange)
|
||||
return;
|
||||
|
||||
m_blockDataChange = true;
|
||||
|
||||
const int row = item->row();
|
||||
QTableWidgetItem *tagItem = ui->tableWidget->item(row, tagsTableColumns::name);
|
||||
const QVariant value = QVariant::fromValue(tagFromTable(row));
|
||||
tagItem->setData(TagTableWidgetItem::TagRole, value);
|
||||
|
||||
m_blockDataChange = false;
|
||||
}
|
||||
|
||||
void ItemTagsLoader::onTableWidgetItemChanged()
|
||||
{
|
||||
for (int row = 0; row < ui->tableWidget->rowCount(); ++row)
|
||||
onTableWidgetItemChanged(ui->tableWidget->item(row, 0));
|
||||
}
|
||||
|
||||
QString ItemTagsLoader::serializeTag(const ItemTagsLoader::Tag &tag)
|
||||
{
|
||||
return escapeTagField(tag.name)
|
||||
+ ";;" + escapeTagField(tag.color)
|
||||
+ ";;" + escapeTagField(tag.icon)
|
||||
+ ";;" + escapeTagField(tag.styleSheet)
|
||||
+ ";;" + escapeTagField(tag.match);
|
||||
}
|
||||
|
||||
ItemTagsLoader::Tag ItemTagsLoader::deserializeTag(const QString &tagText)
|
||||
{
|
||||
QStringList tagFields = tagText.split(";;");
|
||||
|
||||
Tag tag;
|
||||
tag.name = unescapeTagField(tagFields.value(0));
|
||||
tag.color = unescapeTagField(tagFields.value(1));
|
||||
tag.icon = unescapeTagField(tagFields.value(2));
|
||||
tag.styleSheet = unescapeTagField(tagFields.value(3));
|
||||
tag.match = unescapeTagField(tagFields.value(4));
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
ItemTagsLoader::Tags ItemTagsLoader::toTags(const QString &tagsContent)
|
||||
{
|
||||
Tags tags;
|
||||
|
||||
for (const auto &tagText : tagsContent.split(',', QString::SkipEmptyParts)) {
|
||||
QString tagName = tagText.trimmed();
|
||||
Tag tag = findMatchingTag(tagName, m_tags);
|
||||
|
||||
if (isTagValid(tag)) {
|
||||
if (tag.match.isEmpty()) {
|
||||
tag.name = tagName;
|
||||
} else {
|
||||
const QRegExp re(tag.match);
|
||||
tag.name = QString(tagName).replace(re, tag.name);
|
||||
}
|
||||
} else {
|
||||
tag.name = tagName;
|
||||
|
||||
// Get default tag style from theme.
|
||||
const QSettings settings;
|
||||
tag.color = settings.value("Theme/num_fg").toString();
|
||||
}
|
||||
|
||||
tags.append(tag);
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
void ItemTagsLoader::addTagToSettingsTable(const ItemTagsLoader::Tag &tag)
|
||||
{
|
||||
QTableWidget *t = ui->tableWidget;
|
||||
|
||||
const int row = t->rowCount();
|
||||
|
||||
t->insertRow(row);
|
||||
t->setItem( row, tagsTableColumns::name, new TagTableWidgetItem(tag.name) );
|
||||
t->setItem( row, tagsTableColumns::match, new QTableWidgetItem(tag.match) );
|
||||
t->setItem( row, tagsTableColumns::styleSheet, new QTableWidgetItem(tag.styleSheet) );
|
||||
t->setItem( row, tagsTableColumns::color, new QTableWidgetItem() );
|
||||
t->setItem( row, tagsTableColumns::icon, new QTableWidgetItem() );
|
||||
|
||||
auto colorButton = new QPushButton(t);
|
||||
const QColor color = tag.color.isEmpty()
|
||||
? QColor::fromRgb(50, 50, 50)
|
||||
: deserializeColor(tag.color);
|
||||
setColorIcon(colorButton, color);
|
||||
t->setCellWidget(row, tagsTableColumns::color, colorButton);
|
||||
connect(colorButton, SIGNAL(clicked()), SLOT(onColorButtonClicked()));
|
||||
|
||||
auto iconButton = new IconSelectButton(t);
|
||||
iconButton->setCurrentIcon(tag.icon);
|
||||
t->setCellWidget(row, tagsTableColumns::icon, iconButton);
|
||||
connect(iconButton, SIGNAL(currentIconChanged(QString)), SLOT(onTableWidgetItemChanged()));
|
||||
|
||||
onTableWidgetItemChanged(t->item(row, 0));
|
||||
}
|
||||
|
||||
ItemTagsLoader::Tag ItemTagsLoader::tagFromTable(int row)
|
||||
{
|
||||
QTableWidget *t = ui->tableWidget;
|
||||
|
||||
Tag tag;
|
||||
tag.name = t->item(row, tagsTableColumns::name)->text();
|
||||
const QColor color =
|
||||
cellWidgetProperty(t, row, tagsTableColumns::color, propertyColor).value<QColor>();
|
||||
tag.color = serializeColor(color);
|
||||
tag.icon = cellWidgetProperty(t, row, tagsTableColumns::icon, "currentIcon").toString();
|
||||
tag.styleSheet = t->item(row, tagsTableColumns::styleSheet)->text();
|
||||
tag.match = t->item(row, tagsTableColumns::match)->text();
|
||||
return tag;
|
||||
}
|
||||
|
||||
Q_EXPORT_PLUGIN2(itemtags, ItemTagsLoader)
|
@ -0,0 +1,184 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMTAGS_H
|
||||
#define ITEMTAGS_H
|
||||
|
||||
#include "gui/icons.h"
|
||||
#include "item/itemwidget.h"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Ui {
|
||||
class ItemTagsSettings;
|
||||
}
|
||||
|
||||
class QTableWidgetItem;
|
||||
|
||||
class ItemTags : public QWidget, public ItemWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Tag {
|
||||
QString name;
|
||||
QString color;
|
||||
QString icon;
|
||||
QString styleSheet;
|
||||
QString match;
|
||||
};
|
||||
|
||||
using Tags = QVector<ItemTags::Tag>;
|
||||
|
||||
ItemTags(ItemWidget *childItem, const Tags &tags);
|
||||
|
||||
void setCurrent(bool current) override;
|
||||
|
||||
signals:
|
||||
void runCommand(const Command &command);
|
||||
|
||||
protected:
|
||||
void highlight(const QRegExp &re, const QFont &highlightFont,
|
||||
const QPalette &highlightPalette) override;
|
||||
|
||||
QWidget *createEditor(QWidget *parent) const override;
|
||||
|
||||
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
|
||||
void setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
bool hasChanges(QWidget *editor) const override;
|
||||
|
||||
QObject *createExternalEditor(const QModelIndex &index, QWidget *parent) const override;
|
||||
|
||||
void updateSize(const QSize &maximumSize, int idealWidth) override;
|
||||
|
||||
private:
|
||||
QWidget *m_tagWidget;
|
||||
std::unique_ptr<ItemWidget> m_childItem;
|
||||
};
|
||||
|
||||
class ItemTagsLoader;
|
||||
|
||||
class ItemTagsScriptable : public ItemScriptable
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QStringList userTags READ getUserTags)
|
||||
|
||||
public:
|
||||
explicit ItemTagsScriptable(const QStringList &userTags, QObject *parent);
|
||||
|
||||
QStringList getUserTags() const;
|
||||
|
||||
public slots:
|
||||
QStringList tags();
|
||||
void tag();
|
||||
void untag();
|
||||
void clearTags();
|
||||
bool hasTag();
|
||||
|
||||
private:
|
||||
QString askTagName(const QString &dialogTitle, const QStringList &tags);
|
||||
QString askRemoveTagName(const QStringList &tags);
|
||||
QList<int> rows(const QVariantList &arguments, int skip);
|
||||
QStringList tags(int row);
|
||||
QStringList tags(const QVariant &tags);
|
||||
QStringList tags(const QVariantMap &itemData);
|
||||
void setTags(int row, const QStringList &tags);
|
||||
bool addTag(const QString &tagName, QStringList *tags);
|
||||
bool removeTag(const QString &tagName, QStringList *tags);
|
||||
|
||||
QStringList m_userTags;
|
||||
};
|
||||
|
||||
class ItemTagsLoader : public QObject, public ItemLoaderInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID COPYQ_PLUGIN_ITEM_LOADER_ID)
|
||||
Q_INTERFACES(ItemLoaderInterface)
|
||||
|
||||
public:
|
||||
ItemTagsLoader();
|
||||
~ItemTagsLoader();
|
||||
|
||||
QString id() const override { return "itemtags"; }
|
||||
QString name() const override { return tr("Tags"); }
|
||||
QString author() const override { return QString(); }
|
||||
QString description() const override { return tr("Display tags for items."); }
|
||||
QVariant icon() const override { return QVariant(IconTag); }
|
||||
|
||||
QStringList formatsToSave() const override;
|
||||
|
||||
QVariantMap applySettings() override;
|
||||
|
||||
void loadSettings(const QVariantMap &settings) override;
|
||||
|
||||
QWidget *createSettingsWidget(QWidget *parent) override;
|
||||
|
||||
ItemWidget *transform(ItemWidget *itemWidget, const QModelIndex &index) override;
|
||||
|
||||
bool matches(const QModelIndex &index, const QRegExp &re) const override;
|
||||
|
||||
QObject *tests(const TestInterfacePtr &test) const override;
|
||||
|
||||
const QObject *signaler() const override { return this; }
|
||||
|
||||
ItemScriptable *scriptableObject(QObject *parent) override;
|
||||
|
||||
QList<Command> commands() const override;
|
||||
|
||||
signals:
|
||||
void addCommands(const QList<Command> &commands);
|
||||
|
||||
private slots:
|
||||
void addCommands();
|
||||
|
||||
private slots:
|
||||
void onColorButtonClicked();
|
||||
void onTableWidgetItemChanged(QTableWidgetItem *item);
|
||||
void onTableWidgetItemChanged();
|
||||
|
||||
private:
|
||||
QStringList userTags() const;
|
||||
|
||||
using Tag = ItemTags::Tag;
|
||||
using Tags = ItemTags::Tags;
|
||||
|
||||
static QString serializeTag(const Tag &tag);
|
||||
static Tag deserializeTag(const QString &tagText);
|
||||
|
||||
Tags toTags(const QString &tagsContent);
|
||||
|
||||
void addTagToSettingsTable(const Tag &tag = Tag());
|
||||
|
||||
Tag tagFromTable(int row);
|
||||
|
||||
QVariantMap m_settings;
|
||||
Tags m_tags;
|
||||
std::unique_ptr<Ui::ItemTagsSettings> ui;
|
||||
|
||||
bool m_blockDataChange;
|
||||
};
|
||||
|
||||
#endif // ITEMTAGS_H
|
@ -0,0 +1,21 @@
|
||||
include(../plugins_common.pri)
|
||||
|
||||
HEADERS += itemtags.h \
|
||||
../../src/gui/iconselectbutton.h \
|
||||
../../src/gui/iconselectdialog.h
|
||||
SOURCES += itemtags.cpp \
|
||||
../../src/common/config.cpp \
|
||||
../../src/common/mimetypes.cpp \
|
||||
../../src/common/textdata.cpp \
|
||||
../../src/gui/iconselectbutton.cpp \
|
||||
../../src/gui/iconselectdialog.cpp \
|
||||
../../src/gui/iconfont.cpp
|
||||
FORMS += itemtagssettings.ui
|
||||
|
||||
CONFIG(debug, debug|release) {
|
||||
SOURCES += tests/itemtagstests.cpp
|
||||
HEADERS += tests/itemtagstests.h
|
||||
}
|
||||
|
||||
TARGET = $$qtLibraryTarget(itemtags)
|
||||
|
@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ItemTagsSettings</class>
|
||||
<widget class="QWidget" name="ItemTagsSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>394</width>
|
||||
<height>294</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Menu items for adding and removing custom tags can be added and customized in Commands dialog.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>More info is available on <a href="https://github.com/hluk/CopyQ/wiki/Tags">wiki page</a>.</string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="tableWidget">
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Tag Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Match</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Style Sheet</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Icon</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonAddCommands">
|
||||
<property name="text">
|
||||
<string>Add Actions to Menu and Toolbar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,267 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemtagstests.h"
|
||||
|
||||
#include "common/mimetypes.h"
|
||||
#include "tests/test_utils.h"
|
||||
|
||||
namespace {
|
||||
|
||||
QString testTag(int i)
|
||||
{
|
||||
return "TAG_&" + QString::number(i);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemTagsTests::ItemTagsTests(const TestInterfacePtr &test, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_test(test)
|
||||
{
|
||||
}
|
||||
|
||||
QStringList ItemTagsTests::testTags()
|
||||
{
|
||||
return QStringList()
|
||||
<< testTag(1)
|
||||
<< testTag(2)
|
||||
<< testTag(3)
|
||||
<< testTag(4)
|
||||
<< testTag(5);
|
||||
}
|
||||
|
||||
void ItemTagsTests::initTestCase()
|
||||
{
|
||||
TEST(m_test->initTestCase());
|
||||
}
|
||||
|
||||
void ItemTagsTests::cleanupTestCase()
|
||||
{
|
||||
TEST(m_test->cleanupTestCase());
|
||||
}
|
||||
|
||||
void ItemTagsTests::init()
|
||||
{
|
||||
TEST(m_test->init());
|
||||
}
|
||||
|
||||
void ItemTagsTests::cleanup()
|
||||
{
|
||||
TEST( m_test->cleanup() );
|
||||
}
|
||||
|
||||
void ItemTagsTests::userTags()
|
||||
{
|
||||
RUN("-e" << "plugins.itemtags.userTags",
|
||||
QString(testTags().join("\n") + "\n").toUtf8());
|
||||
}
|
||||
|
||||
void ItemTagsTests::tag()
|
||||
{
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "tab" << tab1;
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "");
|
||||
RUN(args << "add" << "A" << "B" << "C", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(3)", "");
|
||||
RUN(args << "size", "3\n");
|
||||
|
||||
RUN(args << "-e" << "plugins.itemtags.tag('x', 0)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "x\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 0)", "true\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 0)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 1)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 1)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 2)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 2)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(3)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 3)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 3)", "false\n");
|
||||
RUN(args << "size", "3\n");
|
||||
|
||||
RUN(args << "-e" << "plugins.itemtags.tag('y', 0, 1)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "x\ny\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 0)", "true\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 0)", "true\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "y\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 1)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 1)", "true\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 2)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 2)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(3)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 3)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 3)", "false\n");
|
||||
RUN(args << "size", "3\n");
|
||||
|
||||
RUN(args << "-e" << "plugins.itemtags.tag('z', 2, 3, 4)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "x\ny\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 0)", "true\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 0)", "true\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('z', 0)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "y\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 1)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 1)", "true\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('z', 1)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "z\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 2)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 2)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('z', 2)", "true\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(3)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('x', 3)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('y', 3)", "false\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.hasTag('z', 3)", "false\n");
|
||||
RUN(args << "size", "3\n");
|
||||
}
|
||||
|
||||
void ItemTagsTests::untag()
|
||||
{
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "tab" << tab1;
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "");
|
||||
RUN(args << "add" << "A" << "B" << "C", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tag('x', 0, 1)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tag('y', 1, 2)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "x\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "x\ny\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "y\n");
|
||||
|
||||
RUN(args << "-e" << "plugins.itemtags.untag('x', 1)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "x\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "y\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "y\n");
|
||||
|
||||
RUN(args << "-e" << "plugins.itemtags.untag('y', 1, 2)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "x\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "");
|
||||
}
|
||||
|
||||
void ItemTagsTests::clearTags()
|
||||
{
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "tab" << tab1;
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "");
|
||||
RUN(args << "add" << "A" << "B" << "C", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tag('x', 0, 1)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tag('y', 1, 2)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "x\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "x\ny\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "y\n");
|
||||
|
||||
RUN(args << "-e" << "plugins.itemtags.clearTags(1)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "x\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "y\n");
|
||||
|
||||
RUN(args << "-e" << "plugins.itemtags.tag('a', 1, 2)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "x\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "a\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "a\ny\n");
|
||||
|
||||
RUN(args << "-e" << "plugins.itemtags.clearTags(0, 2)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "a\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "");
|
||||
}
|
||||
|
||||
void ItemTagsTests::searchTags()
|
||||
{
|
||||
const QString tab1 = testTab(1);
|
||||
const Args args = Args() << "tab" << tab1;
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "");
|
||||
RUN(args << "add" << "A" << "B" << "C", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tag('tag1', 0, 1)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tag('tag2', 1, 2)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tag('tag3', 2)", "");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(0)", "tag1\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(1)", "tag1\ntag2\n");
|
||||
RUN(args << "-e" << "plugins.itemtags.tags(2)", "tag2\ntag3\n");
|
||||
|
||||
RUN(args << "keys" << "RIGHT", "");
|
||||
RUN(args << "keys" << ":tag1", "");
|
||||
RUN(args << "keys" << "TAB" << "CTRL+A", "");
|
||||
RUN(args << "testSelected", tab1 + " 0 0 1\n");
|
||||
|
||||
RUN(args << "keys" << "ESCAPE", "");
|
||||
RUN(args << "keys" << ":tag2", "");
|
||||
RUN(args << "keys" << "TAB" << "CTRL+A", "");
|
||||
RUN(args << "testSelected", tab1 + " 1 1 2\n");
|
||||
|
||||
RUN(args << "keys" << "ESCAPE", "");
|
||||
RUN(args << "keys" << ":tag3", "");
|
||||
RUN(args << "keys" << "TAB" << "CTRL+A", "");
|
||||
RUN(args << "testSelected", tab1 + " 2 2\n");
|
||||
}
|
||||
|
||||
void ItemTagsTests::tagSelected()
|
||||
{
|
||||
const auto script = R"(
|
||||
setCommands([{
|
||||
name: 'Add Tag x',
|
||||
inMenu: true,
|
||||
shortcuts: ['Ctrl+F1'],
|
||||
cmd: 'copyq: plugins.itemtags.tag("x")'
|
||||
},
|
||||
{
|
||||
name: 'Add Tag y',
|
||||
inMenu: true,
|
||||
shortcuts: ['Ctrl+F2'],
|
||||
cmd: 'copyq: plugins.itemtags.tag("y")'
|
||||
}])
|
||||
)";
|
||||
RUN(script, "");
|
||||
|
||||
RUN("add" << "A" << "B" << "C", "");
|
||||
RUN("keys" << "CTRL+F1", "");
|
||||
RUN("plugins.itemtags.tags(0)", "x\n");
|
||||
|
||||
RUN("selectItems(0,1)", "true\n");
|
||||
RUN("keys" << "CTRL+F2", "");
|
||||
RUN("plugins.itemtags.tags(0)", "x\ny\n");
|
||||
RUN("plugins.itemtags.tags(1)", "y\n");
|
||||
}
|
||||
|
||||
void ItemTagsTests::untagSelected()
|
||||
{
|
||||
const auto script = R"(
|
||||
setCommands([{
|
||||
name: 'Remove Tag x',
|
||||
inMenu: true,
|
||||
shortcuts: ['Ctrl+F1'],
|
||||
cmd: 'copyq: plugins.itemtags.untag("x")'
|
||||
}])
|
||||
)";
|
||||
RUN(script, "");
|
||||
|
||||
RUN("add" << "A" << "B" << "C", "");
|
||||
RUN("plugins.itemtags.tag('x', 0, 2)", "");
|
||||
RUN("plugins.itemtags.tag('y', 1, 2)", "");
|
||||
|
||||
RUN("selectItems(0,1,2)", "true\n");
|
||||
RUN("keys" << "CTRL+F1", "");
|
||||
RUN("plugins.itemtags.tags(0)", "");
|
||||
RUN("plugins.itemtags.tags(1)", "y\n");
|
||||
RUN("plugins.itemtags.tags(2)", "y\n");
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ITEMSYNCTESTS_H
|
||||
#define ITEMSYNCTESTS_H
|
||||
|
||||
#include "tests/testinterface.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class ItemTagsTests : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ItemTagsTests(const TestInterfacePtr &test, QObject *parent = nullptr);
|
||||
|
||||
static QStringList testTags();
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
void userTags();
|
||||
void tag();
|
||||
void untag();
|
||||
void clearTags();
|
||||
void searchTags();
|
||||
|
||||
void tagSelected();
|
||||
void untagSelected();
|
||||
|
||||
private:
|
||||
TestInterfacePtr m_test;
|
||||
};
|
||||
|
||||
#endif // ITEMSYNCTESTS_H
|
@ -0,0 +1,6 @@
|
||||
set(copyq_plugin_itemtext_SOURCES
|
||||
../../src/common/mimetypes.cpp
|
||||
)
|
||||
|
||||
copyq_add_plugin(itemtext)
|
||||
|
@ -0,0 +1,253 @@
|
||||
/*
|
||||
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
|
||||
|
||||
This file is part of CopyQ.
|
||||
|
||||
CopyQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
CopyQ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itemtext.h"
|
||||
#include "ui_itemtextsettings.h"
|
||||
|
||||
#include "common/contenttype.h"
|
||||
#include "common/mimetypes.h"
|
||||
|
||||
#include <QContextMenuEvent>
|
||||
#include <QModelIndex>
|
||||
#include <QMouseEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QTextBlock>
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocument>
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QtPlugin>
|
||||
|
||||
namespace {
|
||||
|
||||
// Limit number of characters for performance reasons.
|
||||
const int defaultMaxBytes = 100*1024;
|
||||
|
||||
const char optionUseRichText[] = "use_rich_text";
|
||||
const char optionMaximumLines[] = "max_lines";
|
||||
const char optionMaximumHeight[] = "max_height";
|
||||
|
||||
const char mimeRichText[] = "text/richtext";
|
||||
|
||||
// Some applications insert \0 teminator at the end of text data.
|
||||
// It needs to be removed because QTextBrowser can render the character.
|
||||
void removeTrailingNull(QString *text)
|
||||
{
|
||||
if ( text->endsWith(QChar(0)) )
|
||||
text->chop(1);
|
||||
}
|
||||
|
||||
bool getRichText(const QModelIndex &index, QString *text)
|
||||
{
|
||||
if ( index.data(contentType::hasHtml).toBool() ) {
|
||||
*text = index.data(contentType::html).toString();
|
||||
return true;
|
||||
}
|
||||
|
||||
const QVariantMap dataMap = index.data(contentType::data).toMap();
|
||||
if ( !dataMap.contains(mimeRichText) )
|
||||
return false;
|
||||
|
||||
const QByteArray data = dataMap[mimeRichText].toByteArray();
|
||||
*text = QString::fromUtf8(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getText(const QModelIndex &index, QString *text)
|
||||
{
|
||||
if ( index.data(contentType::hasText).toBool() ) {
|
||||
*text = index.data(contentType::text).toString();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString normalizeText(QString text)
|
||||
{
|
||||
removeTrailingNull(&text);
|
||||
return text.left(defaultMaxBytes);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemText::ItemText(const QString &text, bool isRichText, int maxLines, int maximumHeight, QWidget *parent)
|
||||
: QTextBrowser(parent)
|
||||
, ItemWidget(this)
|
||||
, m_textDocument()
|
||||
, m_maximumHeight(maximumHeight)
|
||||
{
|
||||
m_textDocument.setDefaultFont(font());
|
||||
|
||||
setReadOnly(true);
|
||||
setUndoRedoEnabled(false);
|
||||
setOpenExternalLinks(true);
|
||||
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setFrameStyle(QFrame::NoFrame);
|
||||
|
||||
setContextMenuPolicy(Qt::NoContextMenu);
|
||||
|
||||
if (isRichText)
|
||||
m_textDocument.setHtml( normalizeText(text) );
|
||||
else
|
||||
m_textDocument.setPlainText( normalizeText(text) );
|
||||
|
||||
m_textDocument.setDocumentMargin(0);
|
||||
|
||||
setProperty("CopyQ_no_style", isRichText);
|
||||
|
||||
if (maxLines > 0) {
|
||||
QTextBlock block = m_textDocument.findBlockByLineNumber(maxLines);
|
||||
if (block.isValid()) {
|
||||
QTextCursor tc(&m_textDocument);
|
||||
tc.setPosition(block.position() - 1);
|
||||
tc.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
|
||||
tc.removeSelectedText();
|
||||
tc.insertHtml( " "
|
||||
"<span style='background:rgba(0,0,0,30);border-radius:4px'>"
|
||||
" … "
|
||||
"</span>");
|
||||
}
|
||||
}
|
||||
|
||||
setDocument(&m_textDocument);
|
||||
}
|
||||
|
||||
void ItemText::highlight(const QRegExp &re, const QFont &highlightFont, const QPalette &highlightPalette)
|
||||
{
|
||||
QList<QTextBrowser::ExtraSelection> selections;
|
||||
|
||||
if ( !re.isEmpty() ) {
|
||||
QTextBrowser::ExtraSelection selection;
|
||||
selection.format.setBackground( highlightPalette.base() );
|
||||
selection.format.setForeground( highlightPalette.text() );
|
||||
selection.format.setFont(highlightFont);
|
||||
|
||||
QTextCursor cur = m_textDocument.find(re);
|
||||
int a = cur.position();
|
||||
while ( !cur.isNull() ) {
|
||||
if ( cur.hasSelection() ) {
|
||||
selection.cursor = cur;
|
||||
selections.append(selection);
|
||||
} else {
|
||||
cur.movePosition(QTextCursor::NextCharacter);
|
||||
}
|
||||
cur = m_textDocument.find(re, cur);
|
||||
int b = cur.position();
|
||||
if (a == b) {
|
||||
cur.movePosition(QTextCursor::NextCharacter);
|
||||
cur = m_textDocument.find(re, cur);
|
||||
b = cur.position();
|
||||
if (a == b) break;
|
||||
}
|
||||
a = b;
|
||||
}
|
||||
}
|
||||
|
||||
setExtraSelections(selections);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void ItemText::updateSize(const QSize &maximumSize, int idealWidth)
|
||||
{
|
||||
const int scrollBarWidth = verticalScrollBar()->isVisible() ? verticalScrollBar()->width() : 0;
|
||||
setMaximumHeight( maximumSize.height() );
|
||||
setFixedWidth(idealWidth);
|
||||
m_textDocument.setTextWidth(idealWidth - scrollBarWidth);
|
||||
|
||||
QTextOption option = m_textDocument.defaultTextOption();
|
||||
const QTextOption::WrapMode wrapMode = maximumSize.width() > idealWidth
|
||||
? QTextOption::NoWrap : QTextOption::WrapAtWordBoundaryOrAnywhere;
|
||||
if (wrapMode != option.wrapMode()) {
|
||||
option.setWrapMode(wrapMode);
|
||||
m_textDocument.setDefaultTextOption(option);
|
||||
}
|
||||
|
||||
const QRectF rect = m_textDocument.documentLayout()->frameBoundingRect(m_textDocument.rootFrame());
|
||||
setFixedWidth( static_cast<int>(rect.right()) );
|
||||
|
||||
QTextCursor tc(&m_textDocument);
|
||||
tc.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
|
||||
const auto h = static_cast<int>( cursorRect(tc).bottom() + 4 * logicalDpiY() / 96.0 );
|
||||
setFixedHeight(0 < m_maximumHeight && m_maximumHeight < h ? m_maximumHeight : h);
|
||||
}
|
||||
|
||||
bool ItemText::eventFilter(QObject *, QEvent *event)
|
||||
{
|
||||
return ItemWidget::filterMouseEvents(this, event);
|
||||
}
|
||||
|
||||
ItemTextLoader::ItemTextLoader()
|
||||
{
|
||||
}
|
||||
|
||||
ItemTextLoader::~ItemTextLoader() = default;
|
||||
|
||||
ItemWidget *ItemTextLoader::create(const QModelIndex &index, QWidget *parent, bool preview) const
|
||||
{
|
||||
if ( index.data(contentType::isHidden).toBool() )
|
||||
return nullptr;
|
||||
|
||||
QString text;
|
||||
bool isRichText = m_settings.value(optionUseRichText, true).toBool()
|
||||
&& getRichText(index, &text);
|
||||
|
||||
if ( !isRichText && !getText(index, &text) )
|
||||
return nullptr;
|
||||
|
||||
const int maxLines = preview ? 0 : m_settings.value(optionMaximumLines, 0).toInt();
|
||||
const int maxHeight = preview ? 0 : m_settings.value(optionMaximumHeight, 0).toInt();
|
||||
auto item = new ItemText(text, isRichText, maxLines, maxHeight, parent);
|
||||
|
||||
// Allow faster selection in preview window.
|
||||
if (!preview)
|
||||
item->viewport()->installEventFilter(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
QStringList ItemTextLoader::formatsToSave() const
|
||||
{
|
||||
return m_settings.value(optionUseRichText, true).toBool()
|
||||
? QStringList(mimeText) << mimeHtml << mimeRichText
|
||||
: QStringList(mimeText);
|
||||
}
|
||||
|
||||
QVariantMap ItemTextLoader::applySettings()
|
||||
{
|
||||
m_settings[optionUseRichText] = ui->checkBoxUseRichText->isChecked();
|
||||
m_settings[optionMaximumLines] = ui->spinBoxMaxLines->value();
|
||||
m_settings[optionMaximumHeight] = ui->spinBoxMaxHeight->value();
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
QWidget *ItemTextLoader::createSettingsWidget(QWidget *parent)
|
||||
{
|
||||
ui.reset(new Ui::ItemTextSettings);
|
||||
QWidget *w = new QWidget(parent);
|
||||
ui->setupUi(w);
|
||||
ui->checkBoxUseRichText->setChecked( m_settings.value(optionUseRichText, true).toBool() );
|
||||
ui->spinBoxMaxLines->setValue( m_settings.value(optionMaximumLines, 0).toInt() );
|
||||
ui->spinBoxMaxHeight->setValue( m_settings.value(optionMaximumHeight, 0).toInt() );
|
||||
return w;
|
||||
}
|
||||
|
||||
Q_EXPORT_PLUGIN2(itemtext, ItemTextLoader)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue