parent
2a1ab71841
commit
16da6acfa0
@ -1,49 +0,0 @@
|
|||||||
# 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
|
|
@ -1,59 +0,0 @@
|
|||||||
# 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
|
|
@ -1,62 +0,0 @@
|
|||||||
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
|
|
@ -1,21 +0,0 @@
|
|||||||
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>
|
|
@ -1,461 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
@ -1,202 +0,0 @@
|
|||||||
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
@ -1 +0,0 @@
|
|||||||
https://github.com/hluk/CopyQ/wiki/Development
|
|
@ -1,30 +0,0 @@
|
|||||||
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`.
|
|
@ -1,674 +0,0 @@
|
|||||||
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>.
|
|
@ -1,210 +0,0 @@
|
|||||||
# 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).
|
|
@ -1,46 +0,0 @@
|
|||||||
# 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.
|
|
@ -1,9 +0,0 @@
|
|||||||
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.
@ -1,22 +0,0 @@
|
|||||||
%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
|
|
@ -1,69 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
copyq (2.9.0~xenial) xenial; urgency=medium
|
|
||||||
|
|
||||||
* v2.9.0
|
|
||||||
|
|
||||||
-- Lukas Holecek <hluk@email.cz> Fri, 10 Mar 2017 10:10:00 +0200
|
|
@ -1 +0,0 @@
|
|||||||
9
|
|
@ -1,45 +0,0 @@
|
|||||||
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
|
|
@ -1,41 +0,0 @@
|
|||||||
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
|
|
@ -1,124 +0,0 @@
|
|||||||
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"
|
|
@ -1,17 +0,0 @@
|
|||||||
#!/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
|
|
@ -1,17 +0,0 @@
|
|||||||
#!/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
|
|
@ -1,55 +0,0 @@
|
|||||||
%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
|
|
@ -1,23 +0,0 @@
|
|||||||
/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.
@ -1,11 +0,0 @@
|
|||||||
.//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
|
|
@ -1,674 +0,0 @@
|
|||||||
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>.
|
|
@ -1,66 +0,0 @@
|
|||||||
/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
|
|
@ -1,65 +0,0 @@
|
|||||||
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")
|
|
@ -1,8 +0,0 @@
|
|||||||
set(copyq_plugin_itemdata_SOURCES
|
|
||||||
../../src/common/log.cpp
|
|
||||||
../../src/common/mimetypes.cpp
|
|
||||||
../../src/common/textdata.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
copyq_add_plugin(itemdata)
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
|||||||
/*
|
|
||||||
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)
|
|
@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,10 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
@ -1,214 +0,0 @@
|
|||||||
<?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>
|
|
@ -1,13 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
@ -1,891 +0,0 @@
|
|||||||
/*
|
|
||||||
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)
|
|
@ -1,165 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,23 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
|||||||
<?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>
|
|
@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
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";
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,42 +0,0 @@
|
|||||||
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()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
|||||||
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.
|
|
Before Width: | Height: | Size: 372 B |
@ -1,14 +0,0 @@
|
|||||||
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
|
|
@ -1,3 +0,0 @@
|
|||||||
TEMPLATE = lib
|
|
||||||
|
|
||||||
include(fakevim.pri)
|
|
@ -1,236 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** 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
|
|
@ -1,144 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** 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
@ -1,176 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** 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
|
|
@ -1,111 +0,0 @@
|
|||||||
/**************************************************************************
|
|
||||||
**
|
|
||||||
** 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
|
|
@ -1,39 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** 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
|
|
@ -1,49 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** 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
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
|||||||
INCLUDEPATH += $$PWD
|
|
||||||
|
|
||||||
SOURCES += $$PWD/qtcassert.cpp
|
|
@ -1,43 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** 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
|
|
@ -1,608 +0,0 @@
|
|||||||
/*
|
|
||||||
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"
|
|
@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,16 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
|||||||
<RCC>
|
|
||||||
<qresource prefix="/">
|
|
||||||
<file>fakevim/fakevim.png</file>
|
|
||||||
</qresource>
|
|
||||||
</RCC>
|
|
@ -1,48 +0,0 @@
|
|||||||
<?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>
|
|
@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
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");
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,8 +0,0 @@
|
|||||||
set(copyq_plugin_itemimage_SOURCES
|
|
||||||
../../src/common/log.cpp
|
|
||||||
../../src/common/mimetypes.cpp
|
|
||||||
../../src/item/itemeditor.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
copyq_add_plugin(itemimage)
|
|
||||||
|
|
@ -1,237 +0,0 @@
|
|||||||
/*
|
|
||||||
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)
|
|
@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,13 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
|||||||
<?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>
|
|
@ -1,7 +0,0 @@
|
|||||||
set(copyq_plugin_itemnotes_SOURCES
|
|
||||||
../../src/gui/iconfont.cpp
|
|
||||||
../../src/gui/iconwidget.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
copyq_add_plugin(itemnotes)
|
|
||||||
|
|
@ -1,341 +0,0 @@
|
|||||||
/*
|
|
||||||
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)
|
|
@ -1,113 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,10 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
|||||||
<?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>
|
|
@ -1,6 +0,0 @@
|
|||||||
set(copyq_plugin_itempinned_SOURCES
|
|
||||||
../../src/common/display.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
copyq_add_plugin(itempinned)
|
|
||||||
|
|
@ -1,408 +0,0 @@
|
|||||||
/*
|
|
||||||
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)
|
|
@ -1,164 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
|||||||
<?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>
|
|
@ -1,164 +0,0 @@
|
|||||||
/*
|
|
||||||
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");
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,13 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
@ -1,800 +0,0 @@
|
|||||||
/*
|
|
||||||
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;
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,870 +0,0 @@
|
|||||||
/*
|
|
||||||
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)
|
|
@ -1,194 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,30 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
|||||||
<?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>
|
|
@ -1,546 +0,0 @@
|
|||||||
/*
|
|
||||||
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");
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,12 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
@ -1,910 +0,0 @@
|
|||||||
/*
|
|
||||||
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)
|
|
@ -1,184 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,21 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
|||||||
<?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>
|
|
@ -1,267 +0,0 @@
|
|||||||
/*
|
|
||||||
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");
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
@ -1,6 +0,0 @@
|
|||||||
set(copyq_plugin_itemtext_SOURCES
|
|
||||||
../../src/common/mimetypes.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
copyq_add_plugin(itemtext)
|
|
||||||
|
|
@ -1,253 +0,0 @@
|
|||||||
/*
|
|
||||||
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