mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-03-12 03:31:37 +00:00
Compare commits
34 Commits
7f691d3c31
...
4.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 421a58a61c | |||
|
|
b5a3313f80 | ||
|
|
116ac18e13 | ||
|
|
32dcd90b50 | ||
|
|
c2fef8d624 | ||
|
fd0adc77b3
|
|||
|
6a03e1e399
|
|||
|
ae51842007
|
|||
| ab6acd9e88 | |||
|
6a2c1eb293
|
|||
| 7b4c31d262 | |||
|
|
5553414205 | ||
|
|
b138dfad33 | ||
| 701e6d4bb2 | |||
|
b44d1652b6
|
|||
|
|
990eaaa797 | ||
|
|
348ce95f83 | ||
|
|
3255bdf0a2 | ||
|
|
1058247b44 | ||
|
|
7414f82e28 | ||
|
|
8105bb709f | ||
|
ec628751af
|
|||
|
|
288023d03e | ||
|
|
7740dfca0e | ||
|
1e12ad8d4c
|
|||
|
|
c1d94d6771 | ||
|
|
6bc619055e | ||
|
|
32d66cd19b | ||
|
|
735ba2fd0e | ||
|
|
b16b6ecf4d | ||
|
|
2875448c71 | ||
|
|
51b76385c0 | ||
|
|
b9f8dd6ea0 | ||
|
|
6623b04403 |
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -24,8 +24,8 @@ A clear and concise description of what you expected to happen.
|
|||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
**Desktop (please complete the following information):**
|
||||||
- OS: [e.g. Windows 10 / OSX 10.15 / Ubuntu 20.04]
|
- OS: [e.g. Windows 10 / OSX 10.15 / Ubuntu 20.04 / Arch Linux]
|
||||||
- Version [e.g. 4.0.4]
|
- Version [e.g. 4.1.0]
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context about the problem here. You may include the debug log although it is normally best to attach it as a file.
|
Add any other context about the problem here. You may include the debug log although it is normally best to attach it as a file.
|
||||||
|
|||||||
46
Makefile
46
Makefile
@@ -1,7 +1,7 @@
|
|||||||
PYTHON ?= python3
|
PYTHON ?= python3
|
||||||
PYTHON_VERSION_MINOR := $(shell ${PYTHON} -c "import sys; print(sys.version_info.minor)")
|
PYTHON_VERSION_MINOR := $(shell ${PYTHON} -c "import sys; print(sys.version_info.minor)")
|
||||||
PYRCC5 ?= pyrcc5
|
PYRCC5 ?= pyrcc5
|
||||||
REQ_MINOR_VERSION = 4
|
REQ_MINOR_VERSION = 6
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
|
|
||||||
# Window compatability via Msys2
|
# Window compatability via Msys2
|
||||||
@@ -15,7 +15,7 @@ ifeq ($(shell ${PYTHON} -c "import platform; print(platform.system())"), Windows
|
|||||||
VENV_OPTIONS =
|
VENV_OPTIONS =
|
||||||
else
|
else
|
||||||
BIN = bin
|
BIN = bin
|
||||||
SO = cpython-3$(PYTHON_VERSION_MINOR)*.so
|
SO = *.so
|
||||||
VENV_OPTIONS = --system-site-packages
|
VENV_OPTIONS = --system-site-packages
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -43,16 +43,16 @@ mofiles = $(patsubst %.po,%.mo,$(pofiles))
|
|||||||
vpath %.po $(localedirs)
|
vpath %.po $(localedirs)
|
||||||
vpath %.mo $(localedirs)
|
vpath %.mo $(localedirs)
|
||||||
|
|
||||||
all : | env i18n modules qt/dg_rc.py
|
all: | env i18n modules qt/dg_rc.py
|
||||||
@echo "Build complete! You can run dupeGuru with 'make run'"
|
@echo "Build complete! You can run dupeGuru with 'make run'"
|
||||||
|
|
||||||
run:
|
run:
|
||||||
$(VENV_PYTHON) run.py
|
$(VENV_PYTHON) run.py
|
||||||
|
|
||||||
pyc:
|
pyc: | env
|
||||||
${PYTHON} -m compileall ${packages}
|
${VENV_PYTHON} -m compileall ${packages}
|
||||||
|
|
||||||
reqs :
|
reqs:
|
||||||
ifneq ($(shell test $(PYTHON_VERSION_MINOR) -gt $(REQ_MINOR_VERSION); echo $$?),0)
|
ifneq ($(shell test $(PYTHON_VERSION_MINOR) -gt $(REQ_MINOR_VERSION); echo $$?),0)
|
||||||
$(error "Python 3.${REQ_MINOR_VERSION}+ required. Aborting.")
|
$(error "Python 3.${REQ_MINOR_VERSION}+ required. Aborting.")
|
||||||
endif
|
endif
|
||||||
@@ -63,7 +63,7 @@ endif
|
|||||||
@${PYTHON} -c 'import PyQt5' >/dev/null 2>&1 || \
|
@${PYTHON} -c 'import PyQt5' >/dev/null 2>&1 || \
|
||||||
{ echo "PyQt 5.4+ required. Install it and try again. Aborting"; exit 1; }
|
{ echo "PyQt 5.4+ required. Install it and try again. Aborting"; exit 1; }
|
||||||
|
|
||||||
env : | reqs
|
env: | reqs
|
||||||
ifndef NO_VENV
|
ifndef NO_VENV
|
||||||
@echo "Creating our virtualenv"
|
@echo "Creating our virtualenv"
|
||||||
${PYTHON} -m venv env
|
${PYTHON} -m venv env
|
||||||
@@ -73,40 +73,26 @@ ifndef NO_VENV
|
|||||||
${PYTHON} -m venv --upgrade ${VENV_OPTIONS} env
|
${PYTHON} -m venv --upgrade ${VENV_OPTIONS} env
|
||||||
endif
|
endif
|
||||||
|
|
||||||
build/help : | env
|
build/help: | env
|
||||||
$(VENV_PYTHON) build.py --doc
|
$(VENV_PYTHON) build.py --doc
|
||||||
|
|
||||||
qt/dg_rc.py : qt/dg.qrc
|
qt/dg_rc.py: qt/dg.qrc
|
||||||
$(PYRCC5) qt/dg.qrc > qt/dg_rc.py
|
$(PYRCC5) qt/dg.qrc > qt/dg_rc.py
|
||||||
|
|
||||||
i18n: $(mofiles)
|
i18n: $(mofiles)
|
||||||
|
|
||||||
%.mo : %.po
|
%.mo: %.po
|
||||||
msgfmt -o $@ $<
|
msgfmt -o $@ $<
|
||||||
|
|
||||||
core/pe/_block.$(SO) : core/pe/modules/block.c core/pe/modules/common.c
|
modules: | env
|
||||||
$(PYTHON) hscommon/build_ext.py $^ _block
|
$(VENV_PYTHON) build.py --modules
|
||||||
mv _block.$(SO) core/pe
|
|
||||||
|
|
||||||
core/pe/_cache.$(SO) : core/pe/modules/cache.c core/pe/modules/common.c
|
mergepot: | env
|
||||||
$(PYTHON) hscommon/build_ext.py $^ _cache
|
|
||||||
mv _cache.$(SO) core/pe
|
|
||||||
|
|
||||||
qt/pe/_block_qt.$(SO) : qt/pe/modules/block.c
|
|
||||||
$(PYTHON) hscommon/build_ext.py $^ _block_qt
|
|
||||||
mv _block_qt.$(SO) qt/pe
|
|
||||||
|
|
||||||
modules : core/pe/_block.$(SO) core/pe/_cache.$(SO) qt/pe/_block_qt.$(SO)
|
|
||||||
|
|
||||||
mergepot :
|
|
||||||
$(VENV_PYTHON) build.py --mergepot
|
$(VENV_PYTHON) build.py --mergepot
|
||||||
|
|
||||||
normpo :
|
normpo: | env
|
||||||
$(VENV_PYTHON) build.py --normpo
|
$(VENV_PYTHON) build.py --normpo
|
||||||
|
|
||||||
srcpkg :
|
|
||||||
./scripts/srcpkg.sh
|
|
||||||
|
|
||||||
install: all pyc
|
install: all pyc
|
||||||
mkdir -p ${DESTDIR}${PREFIX}/share/dupeguru
|
mkdir -p ${DESTDIR}${PREFIX}/share/dupeguru
|
||||||
cp -rf ${packages} locale ${DESTDIR}${PREFIX}/share/dupeguru
|
cp -rf ${packages} locale ${DESTDIR}${PREFIX}/share/dupeguru
|
||||||
@@ -123,7 +109,7 @@ installdocs: build/help
|
|||||||
mkdir -p ${DESTDIR}${PREFIX}/share/dupeguru
|
mkdir -p ${DESTDIR}${PREFIX}/share/dupeguru
|
||||||
cp -rf build/help ${DESTDIR}${PREFIX}/share/dupeguru
|
cp -rf build/help ${DESTDIR}${PREFIX}/share/dupeguru
|
||||||
|
|
||||||
uninstall :
|
uninstall:
|
||||||
rm -rf "${DESTDIR}${PREFIX}/share/dupeguru"
|
rm -rf "${DESTDIR}${PREFIX}/share/dupeguru"
|
||||||
rm -f "${DESTDIR}${PREFIX}/bin/dupeguru"
|
rm -f "${DESTDIR}${PREFIX}/bin/dupeguru"
|
||||||
rm -f "${DESTDIR}${PREFIX}/share/applications/dupeguru.desktop"
|
rm -f "${DESTDIR}${PREFIX}/share/applications/dupeguru.desktop"
|
||||||
@@ -134,4 +120,4 @@ clean:
|
|||||||
-rm locale/*/LC_MESSAGES/*.mo
|
-rm locale/*/LC_MESSAGES/*.mo
|
||||||
-rm core/pe/*.$(SO) qt/pe/*.$(SO)
|
-rm core/pe/*.$(SO) qt/pe/*.$(SO)
|
||||||
|
|
||||||
.PHONY : clean srcpkg normpo mergepot modules i18n reqs run pyc install uninstall all
|
.PHONY: clean normpo mergepot modules i18n reqs run pyc install uninstall all
|
||||||
|
|||||||
65
README.md
65
README.md
@@ -1,19 +1,21 @@
|
|||||||
# dupeGuru
|
# dupeGuru
|
||||||
|
|
||||||
[dupeGuru][dupeguru] is a cross-platform (Linux, OS X, Windows) GUI tool to find duplicate files in
|
[dupeGuru][dupeguru] is a cross-platform (Linux, OS X, Windows) GUI tool to find duplicate files in
|
||||||
a system. It's written mostly in Python 3 and has the peculiarity of using
|
a system. It is written mostly in Python 3 and has the peculiarity of using
|
||||||
[multiple GUI toolkits][cross-toolkit], all using the same core Python code. On OS X, the UI layer
|
[multiple GUI toolkits][cross-toolkit], all using the same core Python code. On OS X, the UI layer
|
||||||
is written in Objective-C and uses Cocoa. On Linux, it's written in Python and uses Qt5.
|
is written in Objective-C and uses Cocoa. On Linux, it is written in Python and uses Qt5.
|
||||||
|
|
||||||
The Cocoa UI of dupeGuru is hosted in a separate repo: https://github.com/hsoft/dupeguru-cocoa
|
The Cocoa UI of dupeGuru is hosted in a separate repo: https://github.com/arsenetar/dupeguru-cocoa
|
||||||
|
|
||||||
## Current status
|
## Current status
|
||||||
|
|
||||||
Development has been slow this past year, however very close to getting all the different 4.0.4 releases posted. Most of the work this past year (2019) has been towards packaging the application and issues related to that.
|
2020: various bug fixes and small UI improvements have been added. Packaging for MacOS is still a problem.
|
||||||
|
|
||||||
Still looking for additional help especially with regards to:
|
Still looking for additional help especially with regards to:
|
||||||
- OSX maintenance (reproducing bugs & cocoa version)
|
* OSX maintenance: reproducing bugs & cocoa version, building package with Cocoa UI.
|
||||||
- Linux maintenance (reproducing bugs)
|
* Linux maintenance: reproducing bugs, maintaining PPA repository, Debian package.
|
||||||
|
* Translations: updating missing strings.
|
||||||
|
* Documentation: keeping it up-to-date.
|
||||||
|
|
||||||
## Contents of this folder
|
## Contents of this folder
|
||||||
|
|
||||||
@@ -31,26 +33,57 @@ This folder contains the source for dupeGuru. Its documentation is in `help`, bu
|
|||||||
|
|
||||||
## How to build dupeGuru from source
|
## How to build dupeGuru from source
|
||||||
|
|
||||||
### Windows
|
### Windows & macOS specific additional instructions
|
||||||
For windows instructions see the [Windows Instructions](Windows.md).
|
For windows instructions see the [Windows Instructions](Windows.md).
|
||||||
|
|
||||||
### Prerequisites
|
For macos instructions (qt version) see the [macOS Instructions](macos.md).
|
||||||
|
|
||||||
* [Python 3.5+][python]
|
### Prerequisites
|
||||||
|
* [Python 3.6+][python]
|
||||||
* PyQt5
|
* PyQt5
|
||||||
|
|
||||||
### make
|
### System Setup
|
||||||
|
When running in a linux based environment the following system packages or equivalents are needed to build:
|
||||||
|
* python3-pyqt5
|
||||||
|
* python3-wheel (for hsaudiotag3k)
|
||||||
|
* python3-venv (only if using a virtual environment)
|
||||||
|
* python3-dev
|
||||||
|
* build-essential
|
||||||
|
|
||||||
dupeGuru is built with "make":
|
To create packages the following are also needed:
|
||||||
|
* python3-setuptools
|
||||||
|
* debhelper
|
||||||
|
|
||||||
$ make
|
### Building with Make
|
||||||
$ make run
|
dupeGuru comes with a makefile that can be used to build and run:
|
||||||
|
|
||||||
### Generate Debian/Ubuntu package
|
$ make && make run
|
||||||
|
|
||||||
$ bash -c "python3 -m venv --system-site-packages env && source env/bin/activate && pip install -r requirements.txt && python3 build.py --clean && python3 package.py"
|
### Building without Make
|
||||||
|
|
||||||
### Running tests
|
$ cd <dupeGuru directory>
|
||||||
|
$ python3 -m venv --system-site-packages ./env
|
||||||
|
$ source ./env/bin/activate
|
||||||
|
$ pip install -r requirements.txt
|
||||||
|
$ python build.py
|
||||||
|
$ python run.py
|
||||||
|
|
||||||
|
### Generating Debian/Ubuntu package
|
||||||
|
To generate packages the extra requirements in requirements-extra.txt must be installed, the
|
||||||
|
steps are as follows:
|
||||||
|
|
||||||
|
$ cd <dupeGuru directory>
|
||||||
|
$ python3 -m venv --system-site-packages ./env
|
||||||
|
$ source ./env/bin/activate
|
||||||
|
$ pip install -r requirements.txt -r requirements-extra.txt
|
||||||
|
$ python build.py --clean
|
||||||
|
$ python package.py
|
||||||
|
|
||||||
|
This can be made a one-liner (once in the directory) as:
|
||||||
|
|
||||||
|
$ bash -c "python3 -m venv --system-site-packages env && source env/bin/activate && pip install -r requirements.txt -r requirements-extra.txt && python build.py --clean && python package.py"
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
The complete test suite is run with [Tox 1.7+][tox]. If you have it installed system-wide, you
|
The complete test suite is run with [Tox 1.7+][tox]. If you have it installed system-wide, you
|
||||||
don't even need to set up a virtualenv. Just `cd` into the root project folder and run `tox`.
|
don't even need to set up a virtualenv. Just `cd` into the root project folder and run `tox`.
|
||||||
|
|||||||
26
Windows.md
26
Windows.md
@@ -2,24 +2,24 @@
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- [Python 3.5+][python]
|
- [Python 3.6+][python]
|
||||||
- [Visual Studio 2017][vs] or [Visual Studio Build Tools 2017][vsBuildTools] with the Windows 10 SDK
|
- [Visual Studio 2019][vs] or [Visual Studio Build Tools 2019][vsBuildTools] with the Windows 10 SDK
|
||||||
- [nsis][nsis] (for installer creation)
|
- [nsis][nsis] (for installer creation)
|
||||||
- [msys2][msys2] (for using makefile method)
|
- [msys2][msys2] (for using makefile method)
|
||||||
|
|
||||||
When installing Visual Studio or the Visual Studio Build Tools with the Windows 10 SDK on versions of Windows below 10 be sure to make sure that the Universal CRT is installed before installing Visual studio as noted in the [Windows 10 SDK Notes][win10sdk] and found at [KB2999226][KB2999226].
|
NOTE: When installing Visual Studio or the Visual Studio Build Tools with the Windows 10 SDK on versions of Windows below 10 be sure to make sure that the Universal CRT is installed before installing Visual studio as noted in the [Windows 10 SDK Notes][win10sdk] and found at [KB2999226][KB2999226].
|
||||||
|
|
||||||
After installing python it is recommended to update setuptools before compiling packages. To update run (example is for python launcher and 3.7):
|
After installing python it is recommended to update setuptools before compiling packages. To update run (example is for python launcher and 3.8):
|
||||||
|
|
||||||
$ py -3.7 -m pip install --upgrade setuptools
|
$ py -3.8 -m pip install --upgrade setuptools
|
||||||
|
|
||||||
More details on setting up python for compiling packages on windows can be found on the [python wiki][pythonWindowsCompilers]
|
More details on setting up python for compiling packages on windows can be found on the [python wiki][pythonWindowsCompilers] Take note of the required vc++ versions.
|
||||||
|
|
||||||
### With build.py (preferred)
|
### With build.py (preferred)
|
||||||
To build with a different python version 3.5 vs 3.7 or 32 bit vs 64 bit specify that version instead of -3.7 to the `py` command below. If you want to build additional versions while keeping all virtual environments setup use a different location for each vritual environment.
|
To build with a different python version 3.6 vs 3.8 or 32 bit vs 64 bit specify that version instead of -3.8 to the `py` command below. If you want to build additional versions while keeping all virtual environments setup use a different location for each virtual environment.
|
||||||
|
|
||||||
$ cd <dupeGuru directory>
|
$ cd <dupeGuru directory>
|
||||||
$ py -3.7 -m venv .\env
|
$ py -3.8 -m venv .\env
|
||||||
$ .\env\Scripts\activate
|
$ .\env\Scripts\activate
|
||||||
$ pip install -r requirements.txt
|
$ pip install -r requirements.txt
|
||||||
$ python build.py
|
$ python build.py
|
||||||
@@ -34,21 +34,21 @@ It is possible to build dupeGuru with the makefile on windows using a compatable
|
|||||||
Then the following execution of the makefile should work. Pass the correct value for PYTHON to the makefile if not on the path as python3.
|
Then the following execution of the makefile should work. Pass the correct value for PYTHON to the makefile if not on the path as python3.
|
||||||
|
|
||||||
$ cd <dupeGuru directory>
|
$ cd <dupeGuru directory>
|
||||||
$ make PYTHON='py -3.7'
|
$ make PYTHON='py -3.8'
|
||||||
$ make run
|
$ make run
|
||||||
|
|
||||||
### Generate Windows Installer Packages
|
### Generate Windows Installer Packages
|
||||||
You need to use the respective x86 or x64 version of python to build the 32 bit and 64 bit versions. The build scripts will automatically detect the python architecture for you. When using build.py make sure the resulting python works before continuing to package.py. NOTE: package.py looks for the 'makensis' executable in the default location for a 64 bit windows system. Run the following in the respective virtual environment.
|
You need to use the respective x86 or x64 version of python to build the 32 bit and 64 bit versions. The build scripts will automatically detect the python architecture for you. When using build.py make sure the resulting python works before continuing to package.py. NOTE: package.py looks for the 'makensis' executable in the default location for a 64 bit windows system. The extra requirements need to be installed to run packaging: `pip install -r requirements-extra.txt`. Run the following in the respective virtual environment.
|
||||||
|
|
||||||
$ python package.py
|
$ python package.py
|
||||||
|
|
||||||
### Running tests
|
### Running tests
|
||||||
The complete test suite can be run with tox just like on linux.
|
The complete test suite can be run with tox just like on linux. NOTE: The extra requirements need to be installed to run unit tests: `pip install -r requirements-extra.txt`.
|
||||||
|
|
||||||
[python]: http://www.python.org/
|
[python]: http://www.python.org/
|
||||||
[nsis]: http://nsis.sourceforge.net/Main_Page
|
[nsis]: http://nsis.sourceforge.net/Main_Page
|
||||||
[vs]: https://www.visualstudio.com/downloads/#visual-studio-community-2017
|
[vs]: https://www.visualstudio.com/downloads/#visual-studio-community-2019
|
||||||
[vsBuildTools]: https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2017
|
[vsBuildTools]: https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2019
|
||||||
[win10sdk]: https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk
|
[win10sdk]: https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk
|
||||||
[KB2999226]: https://support.microsoft.com/en-us/help/2999226/update-for-universal-c-runtime-in-windows
|
[KB2999226]: https://support.microsoft.com/en-us/help/2999226/update-for-universal-c-runtime-in-windows
|
||||||
[pythonWindowsCompilers]: https://wiki.python.org/moin/WindowsCompilers
|
[pythonWindowsCompilers]: https://wiki.python.org/moin/WindowsCompilers
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
__version__ = "4.0.4"
|
__version__ = "4.1.0"
|
||||||
__appname__ = "dupeGuru"
|
__appname__ = "dupeGuru"
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ class ExcludeListDialogCore:
|
|||||||
"""Sets property on row to highlight if its regex matches test_string supplied."""
|
"""Sets property on row to highlight if its regex matches test_string supplied."""
|
||||||
matched = False
|
matched = False
|
||||||
for row in self.exclude_list_table.rows:
|
for row in self.exclude_list_table.rows:
|
||||||
if self.exclude_list.get_compiled(row.regex).match(test_string):
|
compiled_regex = self.exclude_list.get_compiled(row.regex)
|
||||||
|
if compiled_regex and compiled_regex.match(test_string):
|
||||||
matched = True
|
matched = True
|
||||||
row.highlight = True
|
row.highlight = True
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -72,13 +72,15 @@ class PrioritizeDialog(GUIObject):
|
|||||||
# Add selected criteria in criteria_list to prioritization_list.
|
# Add selected criteria in criteria_list to prioritization_list.
|
||||||
if self.criteria_list.selected_index is None:
|
if self.criteria_list.selected_index is None:
|
||||||
return
|
return
|
||||||
crit = self.criteria[self.criteria_list.selected_index]
|
for i in self.criteria_list.selected_indexes:
|
||||||
self.prioritizations.append(crit)
|
crit = self.criteria[i]
|
||||||
del crit
|
self.prioritizations.append(crit)
|
||||||
|
del crit
|
||||||
self.prioritization_list[:] = [crit.display for crit in self.prioritizations]
|
self.prioritization_list[:] = [crit.display for crit in self.prioritizations]
|
||||||
|
|
||||||
def remove_selected(self):
|
def remove_selected(self):
|
||||||
self.prioritization_list.remove_selected()
|
self.prioritization_list.remove_selected()
|
||||||
|
self.prioritization_list.select([])
|
||||||
|
|
||||||
def perform_reprioritization(self):
|
def perform_reprioritization(self):
|
||||||
self.app.reprioritize_groups(self._sort_key)
|
self.app.reprioritize_groups(self._sort_key)
|
||||||
|
|||||||
@@ -1,3 +1,29 @@
|
|||||||
|
=== 4.1.0 (2020-12-29)
|
||||||
|
|
||||||
|
* Use tabs instead of separate windows (#688)
|
||||||
|
* Show the shortcut for "mark selected" in results dialog (#656, #641)
|
||||||
|
* Add image comparison features to details dialog (#683)
|
||||||
|
* Add the ability to use regex based exclusion filters (#705)
|
||||||
|
* Change reference row background color, and allow user to adjust the color (#701)
|
||||||
|
* Save / Load directories as XML (#706)
|
||||||
|
* Workaround for EXIF IFD type mismatch in parsing function (#630, #698)
|
||||||
|
* Progress dialog stuck at "Verified X/X matches" (#693, #694)
|
||||||
|
* Fix word wrap in ignore list dialog (#687)
|
||||||
|
* Fix issue with result window action on creation (#685)
|
||||||
|
* Colorize details table differences, allow moving rows (#682)
|
||||||
|
* Fix loading Result of 'Scan Type: Folders' shows only '---' in every table cell (#677, #676)
|
||||||
|
* Fix issue with details and results dialog row trimming (#655, #654)
|
||||||
|
* Add option to enable/disable bold font (#646, #314)
|
||||||
|
* Use relative icon path for themes to override more easily (#746)
|
||||||
|
* Fix issues with Python 3.8 compatibility (#665)
|
||||||
|
* Fix flake8 issues (#672)
|
||||||
|
* Update to use newer pytest and expand flake8 checking, cleanup various Deprecation Warnings
|
||||||
|
* Add warnings to packaging script when files are not built (#691)
|
||||||
|
* Use relative icon path for themes to override more easily (#746)
|
||||||
|
* Update Packaging for Ubuntu (#593)
|
||||||
|
* Minor Build Updates (#627, #575, #628, #614)
|
||||||
|
* Update CI builds and add windows CI (#572, #669)
|
||||||
|
|
||||||
=== 4.0.4 (2019-05-13)
|
=== 4.0.4 (2019-05-13)
|
||||||
|
|
||||||
* Update qt/platform.py to support other Unix style OSes (#444)
|
* Update qt/platform.py to support other Unix style OSes (#444)
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ def build_debian_changelog(
|
|||||||
return [s.strip() for s in result if s.strip()]
|
return [s.strip() for s in result if s.strip()]
|
||||||
|
|
||||||
ENTRY_MODEL = (
|
ENTRY_MODEL = (
|
||||||
"{pkg} ({version}-1) {distribution}; urgency=low\n\n{changes}\n "
|
"{pkg} ({version}) {distribution}; urgency=low\n\n{changes}\n "
|
||||||
"-- Virgil Dupras <hsoft@hardcoded.net> {date}\n\n"
|
"-- Virgil Dupras <hsoft@hardcoded.net> {date}\n\n"
|
||||||
)
|
)
|
||||||
CHANGE_MODEL = " * {description}\n"
|
CHANGE_MODEL = " * {description}\n"
|
||||||
|
|||||||
BIN
images/dupeguru.icns
Executable file
BIN
images/dupeguru.icns
Executable file
Binary file not shown.
53
macos.md
Normal file
53
macos.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
## How to build dupeGuru for macos
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Python 3.6+][python]
|
||||||
|
- [Xcode 12.3][xcode] or just Xcode command line tools (older versions can be used if not interested in arm macs)
|
||||||
|
- [Homebrew][homebrew]
|
||||||
|
- [qt5](https://www.qt.io/)
|
||||||
|
|
||||||
|
#### Prerequisite setup
|
||||||
|
1. Install Xcode if desired
|
||||||
|
2. Install [Homebrew][homebrew], if not on the path after install (arm based Macs) create `~/.zshrc`
|
||||||
|
with `export PATH="/opt/homebrew/bin:$PATH"`. Will need to reload terminal or source the file to take
|
||||||
|
affect.
|
||||||
|
3. Install qt5 with `brew`. If you are using a version of macos without system python 3.6+ then you will
|
||||||
|
also need to install that via brew or with pyenv.
|
||||||
|
|
||||||
|
$ brew install qt5
|
||||||
|
|
||||||
|
NOTE: Using `brew` to install qt5 is to allow pyqt5 to build without a native wheel
|
||||||
|
available. If you are using an intel based mac you can probably skip this step.
|
||||||
|
|
||||||
|
4. May need to launch a new terminal to have everything working.
|
||||||
|
|
||||||
|
### With build.py
|
||||||
|
OSX comes with a version of python 3 by default in newer versions of OSX. To produce universal
|
||||||
|
builds either the 3.8 version shipped in macos or 3.9.1 or newer needs to be used. If needing to
|
||||||
|
build pyqt5 from source then the first line below is needed, else it may be omitted. (Path shown is
|
||||||
|
for an arm mac.)
|
||||||
|
|
||||||
|
$ export PATH="/opt/homebrew/opt/qt/bin:$PATH"
|
||||||
|
$ cd <dupeGuru directory>
|
||||||
|
$ python3 -m venv ./env
|
||||||
|
$ source ./env/bin/activate
|
||||||
|
$ pip install -r requirements.txt
|
||||||
|
$ python build.py
|
||||||
|
$ python run.py
|
||||||
|
|
||||||
|
### Generate OSX Packages
|
||||||
|
The extra requirements need to be installed to run packaging: `pip install -r requirements-extra.txt`.
|
||||||
|
Run the following in the respective virtual environment.
|
||||||
|
|
||||||
|
$ python package.py
|
||||||
|
|
||||||
|
This will produce a dupeGuru.app in the dist folder.
|
||||||
|
|
||||||
|
### Running tests
|
||||||
|
The complete test suite can be run with tox just like on linux. NOTE: The extra requirements need to
|
||||||
|
be installed to run unit tests: `pip install -r requirements-extra.txt`.
|
||||||
|
|
||||||
|
[python]: http://www.python.org/
|
||||||
|
[homebrew]: https://brew.sh/
|
||||||
|
[xcode]: https://developer.apple.com/xcode/
|
||||||
37
package.py
37
package.py
@@ -46,11 +46,11 @@ def copy_files_to_package(destpath, packages, with_so):
|
|||||||
# include locale files if they are built otherwise exit as it will break
|
# include locale files if they are built otherwise exit as it will break
|
||||||
# the localization
|
# the localization
|
||||||
if not op.exists("build/locale"):
|
if not op.exists("build/locale"):
|
||||||
print("Locale files are missing. Have you run \"build.py --loc\"? Exiting...")
|
print('Locale files are missing. Have you run "build.py --loc"? Exiting...')
|
||||||
return
|
return
|
||||||
# include help files if they are built otherwise exit as they should be included?
|
# include help files if they are built otherwise exit as they should be included?
|
||||||
if not op.exists("build/help"):
|
if not op.exists("build/help"):
|
||||||
print("Help files are missing. Have you run \"build.py --doc\"? Exiting...")
|
print('Help files are missing. Have you run "build.py --doc"? Exiting...')
|
||||||
return
|
return
|
||||||
shutil.copytree(op.join("build", "help"), op.join(destpath, "help"))
|
shutil.copytree(op.join("build", "help"), op.join(destpath, "help"))
|
||||||
shutil.copytree(op.join("build", "locale"), op.join(destpath, "locale"))
|
shutil.copytree(op.join("build", "locale"), op.join(destpath, "locale"))
|
||||||
@@ -161,11 +161,11 @@ def package_windows():
|
|||||||
# include locale files if they are built otherwise exit as it will break
|
# include locale files if they are built otherwise exit as it will break
|
||||||
# the localization
|
# the localization
|
||||||
if not op.exists("build/locale"):
|
if not op.exists("build/locale"):
|
||||||
print("Locale files are missing. Have you run \"build.py --loc\"? Exiting...")
|
print('Locale files are missing. Have you run "build.py --loc"? Exiting...')
|
||||||
return
|
return
|
||||||
# include help files if they are built otherwise exit as they should be included?
|
# include help files if they are built otherwise exit as they should be included?
|
||||||
if not op.exists("build/help"):
|
if not op.exists("build/help"):
|
||||||
print("Help files are missing. Have you run \"build.py --doc\"? Exiting...")
|
print('Help files are missing. Have you run "build.py --doc"? Exiting...')
|
||||||
return
|
return
|
||||||
# create version information file from template
|
# create version information file from template
|
||||||
try:
|
try:
|
||||||
@@ -211,6 +211,33 @@ def package_windows():
|
|||||||
print_and_do(cmd.format(version_array[0], version_array[1], version_array[2], bits))
|
print_and_do(cmd.format(version_array[0], version_array[1], version_array[2], bits))
|
||||||
|
|
||||||
|
|
||||||
|
def package_macos():
|
||||||
|
# include locale files if they are built otherwise exit as it will break
|
||||||
|
# the localization
|
||||||
|
if not op.exists("build/locale"):
|
||||||
|
print('Locale files are missing. Have you run "build.py --loc"? Exiting...')
|
||||||
|
return
|
||||||
|
# include help files if they are built otherwise exit as they should be included?
|
||||||
|
if not op.exists("build/help"):
|
||||||
|
print('Help files are missing. Have you run "build.py --doc"? Exiting...')
|
||||||
|
return
|
||||||
|
# run pyinstaller from here:
|
||||||
|
import PyInstaller.__main__
|
||||||
|
|
||||||
|
PyInstaller.__main__.run(
|
||||||
|
[
|
||||||
|
"--name=dupeguru",
|
||||||
|
"--windowed",
|
||||||
|
"--noconfirm",
|
||||||
|
"--icon=images/dupeguru.icns",
|
||||||
|
"--osx-bundle-identifier=com.hardcoded-software.dupeguru",
|
||||||
|
"--add-data=build/locale:locale",
|
||||||
|
"--add-data=build/help:help",
|
||||||
|
"run.py",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
if args.src_pkg:
|
if args.src_pkg:
|
||||||
@@ -220,6 +247,8 @@ def main():
|
|||||||
print("Packaging dupeGuru with UI qt")
|
print("Packaging dupeGuru with UI qt")
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
package_windows()
|
package_windows()
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
package_macos()
|
||||||
else:
|
else:
|
||||||
if not args.arch_pkg:
|
if not args.arch_pkg:
|
||||||
distname = distro.id()
|
distname = distro.id()
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ all:
|
|||||||
chmod +x src/run.py
|
chmod +x src/run.py
|
||||||
cp -R src/ "$(CURDIR)/debian/{pkgname}/usr/share/{execname}"
|
cp -R src/ "$(CURDIR)/debian/{pkgname}/usr/share/{execname}"
|
||||||
cp "$(CURDIR)/debian/{execname}.desktop" "$(CURDIR)/debian/{pkgname}/usr/share/applications"
|
cp "$(CURDIR)/debian/{execname}.desktop" "$(CURDIR)/debian/{pkgname}/usr/share/applications"
|
||||||
ln -s "/usr/share/{execname}/dgse_logo_128.png" "$(CURDIR)/debian/{pkgname}/usr/pixmaps/{execname}.png"
|
mkdir -p "$(CURDIR)/debian/{pkgname}/usr/share/pixmaps"
|
||||||
|
ln -s "/usr/share/{execname}/dgse_logo_128.png" "$(CURDIR)/debian/{pkgname}/usr/share/pixmaps/{execname}.png"
|
||||||
ln -s "/usr/share/{execname}/run.py" "$(CURDIR)/debian/{pkgname}/usr/bin/{execname}"
|
ln -s "/usr/share/{execname}/run.py" "$(CURDIR)/debian/{pkgname}/usr/bin/{execname}"
|
||||||
|
|||||||
78
qt/app.py
78
qt/app.py
@@ -65,20 +65,25 @@ class DupeGuru(QObject):
|
|||||||
self.recentResults.mustOpenItem.connect(self.model.load_from)
|
self.recentResults.mustOpenItem.connect(self.model.load_from)
|
||||||
self.resultWindow = None
|
self.resultWindow = None
|
||||||
if self.use_tabs:
|
if self.use_tabs:
|
||||||
self.main_window = TabBarWindow(self) if not self.prefs.tabs_default_pos else TabWindow(self)
|
self.main_window = (
|
||||||
|
TabBarWindow(self)
|
||||||
|
if not self.prefs.tabs_default_pos
|
||||||
|
else TabWindow(self)
|
||||||
|
)
|
||||||
parent_window = self.main_window
|
parent_window = self.main_window
|
||||||
self.directories_dialog = self.main_window.createPage("DirectoriesDialog", app=self)
|
self.directories_dialog = self.main_window.createPage(
|
||||||
|
"DirectoriesDialog", app=self
|
||||||
|
)
|
||||||
self.main_window.addTab(
|
self.main_window.addTab(
|
||||||
self.directories_dialog, "Directories", switch=False)
|
self.directories_dialog, "Directories", switch=False
|
||||||
|
)
|
||||||
self.actionDirectoriesWindow.setEnabled(False)
|
self.actionDirectoriesWindow.setEnabled(False)
|
||||||
else: # floating windows only
|
else: # floating windows only
|
||||||
self.main_window = None
|
self.main_window = None
|
||||||
self.directories_dialog = DirectoriesDialog(self)
|
self.directories_dialog = DirectoriesDialog(self)
|
||||||
parent_window = self.directories_dialog
|
parent_window = self.directories_dialog
|
||||||
|
|
||||||
self.progress_window = ProgressWindow(
|
self.progress_window = ProgressWindow(parent_window, self.model.progress_window)
|
||||||
parent_window, self.model.progress_window
|
|
||||||
)
|
|
||||||
self.problemDialog = ProblemDialog(
|
self.problemDialog = ProblemDialog(
|
||||||
parent=parent_window, model=self.model.problem_dialog
|
parent=parent_window, model=self.model.problem_dialog
|
||||||
)
|
)
|
||||||
@@ -86,22 +91,25 @@ class DupeGuru(QObject):
|
|||||||
self.ignoreListDialog = self.main_window.createPage(
|
self.ignoreListDialog = self.main_window.createPage(
|
||||||
"IgnoreListDialog",
|
"IgnoreListDialog",
|
||||||
parent=self.main_window,
|
parent=self.main_window,
|
||||||
model=self.model.ignore_list_dialog)
|
model=self.model.ignore_list_dialog,
|
||||||
|
)
|
||||||
|
|
||||||
self.excludeListDialog = self.main_window.createPage(
|
self.excludeListDialog = self.main_window.createPage(
|
||||||
"ExcludeListDialog",
|
"ExcludeListDialog",
|
||||||
app=self,
|
app=self,
|
||||||
parent=self.main_window,
|
parent=self.main_window,
|
||||||
model=self.model.exclude_list_dialog)
|
model=self.model.exclude_list_dialog,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.ignoreListDialog = IgnoreListDialog(
|
self.ignoreListDialog = IgnoreListDialog(
|
||||||
parent=parent_window, model=self.model.ignore_list_dialog)
|
parent=parent_window, model=self.model.ignore_list_dialog
|
||||||
|
)
|
||||||
self.excludeDialog = ExcludeListDialog(
|
self.excludeDialog = ExcludeListDialog(
|
||||||
app=self, parent=parent_window, model=self.model.exclude_list_dialog)
|
app=self, parent=parent_window, model=self.model.exclude_list_dialog
|
||||||
|
)
|
||||||
|
|
||||||
self.deletionOptions = DeletionOptions(
|
self.deletionOptions = DeletionOptions(
|
||||||
parent=parent_window,
|
parent=parent_window, model=self.model.deletion_options
|
||||||
model=self.model.deletion_options
|
|
||||||
)
|
)
|
||||||
self.about_box = AboutBox(parent_window, self)
|
self.about_box = AboutBox(parent_window, self)
|
||||||
|
|
||||||
@@ -129,7 +137,13 @@ class DupeGuru(QObject):
|
|||||||
self.preferencesTriggered,
|
self.preferencesTriggered,
|
||||||
),
|
),
|
||||||
("actionIgnoreList", "", "", tr("Ignore List"), self.ignoreListTriggered),
|
("actionIgnoreList", "", "", tr("Ignore List"), self.ignoreListTriggered),
|
||||||
("actionDirectoriesWindow", "", "", tr("Directories"), self.showDirectoriesWindow),
|
(
|
||||||
|
"actionDirectoriesWindow",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
tr("Directories"),
|
||||||
|
self.showDirectoriesWindow,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"actionClearPictureCache",
|
"actionClearPictureCache",
|
||||||
"Ctrl+Shift+P",
|
"Ctrl+Shift+P",
|
||||||
@@ -137,7 +151,13 @@ class DupeGuru(QObject):
|
|||||||
tr("Clear Picture Cache"),
|
tr("Clear Picture Cache"),
|
||||||
self.clearPictureCacheTriggered,
|
self.clearPictureCacheTriggered,
|
||||||
),
|
),
|
||||||
("actionExcludeList", "", "", tr("Exclusion Filters"), self.excludeListTriggered),
|
(
|
||||||
|
"actionExcludeList",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
tr("Exclusion Filters"),
|
||||||
|
self.excludeListTriggered,
|
||||||
|
),
|
||||||
("actionShowHelp", "F1", "", tr("dupeGuru Help"), self.showHelpTriggered),
|
("actionShowHelp", "F1", "", tr("dupeGuru Help"), self.showHelpTriggered),
|
||||||
("actionAbout", "", "", tr("About dupeGuru"), self.showAboutBoxTriggered),
|
("actionAbout", "", "", tr("About dupeGuru"), self.showAboutBoxTriggered),
|
||||||
(
|
(
|
||||||
@@ -232,8 +252,7 @@ class DupeGuru(QObject):
|
|||||||
if self.resultWindow is not None:
|
if self.resultWindow is not None:
|
||||||
if self.use_tabs:
|
if self.use_tabs:
|
||||||
if self.main_window.indexOfWidget(self.resultWindow) < 0:
|
if self.main_window.indexOfWidget(self.resultWindow) < 0:
|
||||||
self.main_window.addTab(
|
self.main_window.addTab(self.resultWindow, "Results", switch=True)
|
||||||
self.resultWindow, "Results", switch=True)
|
|
||||||
return
|
return
|
||||||
self.main_window.showTab(self.resultWindow)
|
self.main_window.showTab(self.resultWindow)
|
||||||
else:
|
else:
|
||||||
@@ -265,9 +284,11 @@ class DupeGuru(QObject):
|
|||||||
"scanning have accented letters, you'll probably get a crash. It is advised that "
|
"scanning have accented letters, you'll probably get a crash. It is advised that "
|
||||||
"you set your system locale properly."
|
"you set your system locale properly."
|
||||||
)
|
)
|
||||||
QMessageBox.warning(self.main_window if self.main_window
|
QMessageBox.warning(
|
||||||
else self.directories_dialog,
|
self.main_window if self.main_window else self.directories_dialog,
|
||||||
"Wrong Locale", msg)
|
"Wrong Locale",
|
||||||
|
msg,
|
||||||
|
)
|
||||||
|
|
||||||
def clearPictureCacheTriggered(self):
|
def clearPictureCacheTriggered(self):
|
||||||
title = tr("Clear Picture Cache")
|
title = tr("Clear Picture Cache")
|
||||||
@@ -293,7 +314,9 @@ class DupeGuru(QObject):
|
|||||||
"""Add tab for dialog, name the tab with desc_string, then show it."""
|
"""Add tab for dialog, name the tab with desc_string, then show it."""
|
||||||
index = self.main_window.indexOfWidget(dialog)
|
index = self.main_window.indexOfWidget(dialog)
|
||||||
# Create the tab if it doesn't exist already
|
# Create the tab if it doesn't exist already
|
||||||
if index < 0: # or (not dialog.isVisible() and not self.main_window.isTabVisible(index)):
|
if (
|
||||||
|
index < 0
|
||||||
|
): # or (not dialog.isVisible() and not self.main_window.isTabVisible(index)):
|
||||||
index = self.main_window.addTab(dialog, desc_string, switch=True)
|
index = self.main_window.addTab(dialog, desc_string, switch=True)
|
||||||
# Show the tab for that widget
|
# Show the tab for that widget
|
||||||
self.main_window.setCurrentIndex(index)
|
self.main_window.setCurrentIndex(index)
|
||||||
@@ -304,8 +327,7 @@ class DupeGuru(QObject):
|
|||||||
|
|
||||||
def preferencesTriggered(self):
|
def preferencesTriggered(self):
|
||||||
preferences_dialog = self._get_preferences_dialog_class()(
|
preferences_dialog = self._get_preferences_dialog_class()(
|
||||||
self.main_window if self.main_window else self.directories_dialog,
|
self.main_window if self.main_window else self.directories_dialog, self
|
||||||
self
|
|
||||||
)
|
)
|
||||||
preferences_dialog.load()
|
preferences_dialog.load()
|
||||||
result = preferences_dialog.exec()
|
result = preferences_dialog.exec()
|
||||||
@@ -333,7 +355,7 @@ class DupeGuru(QObject):
|
|||||||
if op.exists(help_path):
|
if op.exists(help_path):
|
||||||
url = QUrl.fromLocalFile(help_path)
|
url = QUrl.fromLocalFile(help_path)
|
||||||
else:
|
else:
|
||||||
url = QUrl("https://www.hardcoded.net/dupeguru/help/en/")
|
url = QUrl("https://dupeguru.voltaicideas.net/help/en/")
|
||||||
QDesktopServices.openUrl(url)
|
QDesktopServices.openUrl(url)
|
||||||
|
|
||||||
def handleSIGTERM(self):
|
def handleSIGTERM(self):
|
||||||
@@ -354,8 +376,7 @@ class DupeGuru(QObject):
|
|||||||
return self.confirm("", prompt)
|
return self.confirm("", prompt)
|
||||||
|
|
||||||
def create_results_window(self):
|
def create_results_window(self):
|
||||||
"""Creates resultWindow and details_dialog depending on the selected ``app_mode``.
|
"""Creates resultWindow and details_dialog depending on the selected ``app_mode``."""
|
||||||
"""
|
|
||||||
if self.details_dialog is not None:
|
if self.details_dialog is not None:
|
||||||
# The object is not deleted entirely, avoid saving its geometry in the future
|
# The object is not deleted entirely, avoid saving its geometry in the future
|
||||||
# self.willSavePrefs.disconnect(self.details_dialog.appWillSavePrefs)
|
# self.willSavePrefs.disconnect(self.details_dialog.appWillSavePrefs)
|
||||||
@@ -367,10 +388,13 @@ class DupeGuru(QObject):
|
|||||||
if self.resultWindow is not None:
|
if self.resultWindow is not None:
|
||||||
self.resultWindow.close()
|
self.resultWindow.close()
|
||||||
# This is better for tabs, as it takes care of duplicate items in menu bar
|
# This is better for tabs, as it takes care of duplicate items in menu bar
|
||||||
self.resultWindow.deleteLater() if self.use_tabs else self.resultWindow.setParent(None)
|
self.resultWindow.deleteLater() if self.use_tabs else self.resultWindow.setParent(
|
||||||
|
None
|
||||||
|
)
|
||||||
if self.use_tabs:
|
if self.use_tabs:
|
||||||
self.resultWindow = self.main_window.createPage(
|
self.resultWindow = self.main_window.createPage(
|
||||||
"ResultWindow", parent=self.main_window, app=self)
|
"ResultWindow", parent=self.main_window, app=self
|
||||||
|
)
|
||||||
else: # We don't use a tab widget, regular floating QMainWindow
|
else: # We don't use a tab widget, regular floating QMainWindow
|
||||||
self.resultWindow = ResultWindow(self.directories_dialog, self)
|
self.resultWindow = ResultWindow(self.directories_dialog, self)
|
||||||
self.directories_dialog._updateActionsState()
|
self.directories_dialog._updateActionsState()
|
||||||
|
|||||||
@@ -118,9 +118,7 @@ class ExcludeListDialog(QDialog):
|
|||||||
return
|
return
|
||||||
# if at least one row matched, we know whether table is highlighted or not
|
# if at least one row matched, we know whether table is highlighted or not
|
||||||
self._row_matched = self.model.test_string(input_text)
|
self._row_matched = self.model.test_string(input_text)
|
||||||
# FIXME There is a bug on Windows (7) where the table rows don't get
|
self.table.refresh()
|
||||||
# repainted until the table receives a mouse click event.
|
|
||||||
self.tableView.update()
|
|
||||||
|
|
||||||
input_regex = self.inputLine.text()
|
input_regex = self.inputLine.text()
|
||||||
if not input_regex:
|
if not input_regex:
|
||||||
@@ -148,7 +146,7 @@ class ExcludeListDialog(QDialog):
|
|||||||
if self._row_matched:
|
if self._row_matched:
|
||||||
self._row_matched = False
|
self._row_matched = False
|
||||||
self.model.reset_rows_highlight()
|
self.model.reset_rows_highlight()
|
||||||
self.tableView.update()
|
self.table.refresh()
|
||||||
|
|
||||||
def display_help_message(self):
|
def display_help_message(self):
|
||||||
self.app.show_message(tr("""\
|
self.app.show_message(tr("""\
|
||||||
|
|||||||
@@ -47,9 +47,16 @@ class PrioritizationList(ListviewModel):
|
|||||||
# to know where the drop took place.
|
# to know where the drop took place.
|
||||||
if parentIndex.isValid():
|
if parentIndex.isValid():
|
||||||
return False
|
return False
|
||||||
|
# "When row and column are -1 it means that the dropped data should be considered as
|
||||||
|
# dropped directly on parent."
|
||||||
|
# Moving items to row -1 would put them before the last item. Fix the row to drop the
|
||||||
|
# dragged items after the last item.
|
||||||
|
if row < 0:
|
||||||
|
row = len(self.model) - 1
|
||||||
strMimeData = bytes(mimeData.data(MIME_INDEXES)).decode()
|
strMimeData = bytes(mimeData.data(MIME_INDEXES)).decode()
|
||||||
indexes = list(map(int, strMimeData.split(",")))
|
indexes = list(map(int, strMimeData.split(",")))
|
||||||
self.model.move_indexes(indexes, row)
|
self.model.move_indexes(indexes, row)
|
||||||
|
self.view.selectionModel().clearSelection()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def mimeData(self, indexes):
|
def mimeData(self, indexes):
|
||||||
@@ -84,7 +91,9 @@ class PrioritizeDialog(QDialog):
|
|||||||
self.model.view = self
|
self.model.view = self
|
||||||
|
|
||||||
self.addCriteriaButton.clicked.connect(self.model.add_selected)
|
self.addCriteriaButton.clicked.connect(self.model.add_selected)
|
||||||
|
self.criteriaListView.doubleClicked.connect(self.model.add_selected)
|
||||||
self.removeCriteriaButton.clicked.connect(self.model.remove_selected)
|
self.removeCriteriaButton.clicked.connect(self.model.remove_selected)
|
||||||
|
self.prioritizationListView.doubleClicked.connect(self.model.remove_selected)
|
||||||
self.buttonBox.accepted.connect(self.accept)
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
self.buttonBox.rejected.connect(self.reject)
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
|
||||||
@@ -102,6 +111,7 @@ class PrioritizeDialog(QDialog):
|
|||||||
self.promptLabel.setWordWrap(True)
|
self.promptLabel.setWordWrap(True)
|
||||||
self.categoryCombobox = QComboBox()
|
self.categoryCombobox = QComboBox()
|
||||||
self.criteriaListView = QListView()
|
self.criteriaListView = QListView()
|
||||||
|
self.criteriaListView.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||||
self.addCriteriaButton = QPushButton(
|
self.addCriteriaButton = QPushButton(
|
||||||
self.style().standardIcon(QStyle.SP_ArrowRight), ""
|
self.style().standardIcon(QStyle.SP_ArrowRight), ""
|
||||||
)
|
)
|
||||||
@@ -113,6 +123,7 @@ class PrioritizeDialog(QDialog):
|
|||||||
self.prioritizationListView.setDragEnabled(True)
|
self.prioritizationListView.setDragEnabled(True)
|
||||||
self.prioritizationListView.setDragDropMode(QAbstractItemView.InternalMove)
|
self.prioritizationListView.setDragDropMode(QAbstractItemView.InternalMove)
|
||||||
self.prioritizationListView.setSelectionBehavior(QAbstractItemView.SelectRows)
|
self.prioritizationListView.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
|
self.prioritizationListView.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||||
self.buttonBox = QDialogButtonBox()
|
self.buttonBox = QDialogButtonBox()
|
||||||
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
|
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
|
||||||
|
|
||||||
|
|||||||
@@ -58,9 +58,10 @@ class ErrorReportDialog(QDialog):
|
|||||||
self.verticalLayout.addWidget(self.errorTextEdit)
|
self.verticalLayout.addWidget(self.errorTextEdit)
|
||||||
msg = tr(
|
msg = tr(
|
||||||
"Error reports should be reported as Github issues. You can copy the error traceback "
|
"Error reports should be reported as Github issues. You can copy the error traceback "
|
||||||
"above and paste it in a new issue (bonus point if you run a search to make sure the "
|
"above and paste it in a new issue.\n\nPlease make sure to run a search for any already "
|
||||||
"issue doesn't already exist). What usually really helps is if you add a description "
|
"existing issues beforehand. Also make sure to test the very latest version available from the repository, "
|
||||||
"of how you got the error. Thanks!"
|
"since the bug you are experiencing might have already been patched.\n\n"
|
||||||
|
"What usually really helps is if you add a description of how you got the error. Thanks!"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Although the application should continue to run after this error, it may be in an "
|
"Although the application should continue to run after this error, it may be in an "
|
||||||
"unstable state, so it is recommended that you restart the application."
|
"unstable state, so it is recommended that you restart the application."
|
||||||
|
|||||||
@@ -6,11 +6,14 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QSettings, QRect, QObject, pyqtSignal
|
from PyQt5.QtCore import Qt, QSettings, QRect, QObject, pyqtSignal, QStandardPaths
|
||||||
from PyQt5.QtWidgets import QDockWidget
|
from PyQt5.QtWidgets import QDockWidget
|
||||||
|
|
||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
from hscommon.util import tryint
|
from hscommon.util import tryint
|
||||||
|
from hscommon.plat import ISWINDOWS
|
||||||
|
|
||||||
|
from os import path as op
|
||||||
|
|
||||||
tr = trget("qtlib")
|
tr = trget("qtlib")
|
||||||
|
|
||||||
@@ -74,7 +77,18 @@ class Preferences(QObject):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
QObject.__init__(self)
|
QObject.__init__(self)
|
||||||
self.reset()
|
self.reset()
|
||||||
self._settings = QSettings()
|
# On windows use an ini file in the AppDataLocation instead of registry if possible as it
|
||||||
|
# makes it easier for a user to clear it out when there are issues.
|
||||||
|
if ISWINDOWS:
|
||||||
|
Locations = QStandardPaths.standardLocations(QStandardPaths.AppDataLocation)
|
||||||
|
if Locations:
|
||||||
|
self._settings = QSettings(
|
||||||
|
op.join(Locations[0], "settings.ini"), QSettings.IniFormat
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._settings = QSettings()
|
||||||
|
else:
|
||||||
|
self._settings = QSettings()
|
||||||
|
|
||||||
def _load_values(self, settings, get):
|
def _load_values(self, settings, get):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ pytest>=5,<6
|
|||||||
flake8
|
flake8
|
||||||
tox-travis
|
tox-travis
|
||||||
black
|
black
|
||||||
pyinstaller>=4.0,<5.0; sys_platform == 'win32'
|
pyinstaller>=4.0,<5.0; sys_platform != 'linux'
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
PyQt5 >=5.4,<6.0
|
|
||||||
pywin32>=200
|
|
||||||
pyinstaller>=3.4,<4.0
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
Send2Trash>=1.3.0
|
Send2Trash>=1.3.0
|
||||||
sphinx>=1.2.2
|
sphinx>=1.2.2
|
||||||
polib>=1.0.4
|
polib>=1.0.4
|
||||||
hsaudiotag3k>=1.1.3
|
hsaudiotag3k>=1.1.3*
|
||||||
distro>=1.5.0
|
distro>=1.5.0
|
||||||
PyQt5 >=5.4,<6.0; sys_platform == 'win32'
|
PyQt5 >=5.4,<6.0; sys_platform != 'linux'
|
||||||
pywin32>=200; sys_platform == 'win32'
|
pywin32>=200; sys_platform == 'win32'
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "Creating git archive"
|
|
||||||
version=`python -c "from hscommon.build import get_module_version; print(get_module_version('core'))"`
|
|
||||||
dest="dupeguru-src-${version}.tar"
|
|
||||||
|
|
||||||
git archive -o ${dest} HEAD
|
|
||||||
|
|
||||||
# Now, we need to include submodules
|
|
||||||
submodules="cocoalib"
|
|
||||||
|
|
||||||
for submodule in $submodules; do
|
|
||||||
echo "Adding submodule ${submodule} to archive"
|
|
||||||
archive_name="${submodule}.tar"
|
|
||||||
git -C ${submodule} archive -o ../${archive_name} --prefix ${submodule}/ HEAD
|
|
||||||
tar -A ${archive_name} -f ${dest}
|
|
||||||
rm ${archive_name}
|
|
||||||
done
|
|
||||||
|
|
||||||
xz ${dest}
|
|
||||||
echo "Built source package ${dest}.xz"
|
|
||||||
@@ -48,9 +48,9 @@ SetCompressor /SOLID lzma
|
|||||||
!define APPLICENSE "LICENSE" ; License is not in build directory
|
!define APPLICENSE "LICENSE" ; License is not in build directory
|
||||||
!define APPICON "images\dgse_logo.ico" ; nor is the icon
|
!define APPICON "images\dgse_logo.ico" ; nor is the icon
|
||||||
!define DISTDIR "dist"
|
!define DISTDIR "dist"
|
||||||
!define HELPURL "http://www.hardcoded.net/support/"
|
!define HELPURL "https://github.com/arsenetar/dupeguru/issues"
|
||||||
!define UPDATEURL "http://www.hardcoded.net/dupeguru/"
|
!define UPDATEURL "https://dupeguru.voltaicideas.net/"
|
||||||
!define ABOUTURL "http://www.hardcoded.net/dupeguru/"
|
!define ABOUTURL "https://dupeguru.voltaicideas.net/"
|
||||||
|
|
||||||
; Static Defines
|
; Static Defines
|
||||||
!define UNINSTALLREGBASE "Software\Microsoft\Windows\CurrentVersion\Uninstall"
|
!define UNINSTALLREGBASE "Software\Microsoft\Windows\CurrentVersion\Uninstall"
|
||||||
|
|||||||
Reference in New Issue
Block a user