Commit Graph

2701 Commits

Author SHA1 Message Date
Igor Kushnir
f030a7fb0c Remove deprecated and always disabled alwaysOnTopAction 2022-01-15 18:02:30 +02:00
Igor Kushnir
b3c99823eb MainWindowViewer::createActions(): deduplicate setDisabled 2022-01-15 18:02:30 +02:00
Igor Kushnir
3bb475b47a MainWindowViewer: deduplicate enabling/disabling actions 2022-01-15 18:02:30 +02:00
Igor Kushnir
feaee915bc Extract Viewer::(vertical|horizontal)ScrollStep() 2022-01-15 18:02:30 +02:00
Igor Kushnir
133b547e7a Remove unused *Step* data members from Viewer 2022-01-15 18:02:30 +02:00
Igor Kushnir
88e0f5513a Reader: make 3 keyboard shortcuts work with non-Latin layouts
MainWindowViewer::keyPressEvent()'s custom matching of these shortcuts
does not leverage all the features of standard Qt shortcut matching. As
a result, the corresponding actions cannot be triggered when their
assigned shortcuts consist of a single Latin letter without modifiers
and e.g. Ukrainian keyboard layout is active.

Furthermore, some key presses (e.g. Scroll Lock in my customized
keyboard layout) set QKeyEvent::key() to 0, which the custom matching
considers equal to an unassigned shortcut. So an action without shortcut
is triggered by such a key press.

Adding these 3 actions to MainWindowViewer and connecting the
corresponding slots to their triggered() signals allows to remove the
custom matching code and thus eliminates both of its issues.
2022-01-15 18:02:30 +02:00
Luis Ángel San Martín
77c96de0ea Remove unused resources 2022-01-15 15:51:59 +01:00
Luis Ángel San Martín
e452178adb Log the device pixel ratio used while rendering 2022-01-15 11:54:40 +01:00
Luis Ángel San Martín
08021ba9a8 Take care of QToolButton not handling hdpi properly 2022-01-15 11:32:32 +01:00
Luis Ángel San Martín
565bc3a5d0 Replace all uses of devicePixelRatio with devicePixelRatioF 2022-01-14 19:22:50 +01:00
Luis Ángel San Martín
380aea2a66 Enable Qt::HighDpiScaleFactorRoundingPolicy::PassThrough 2022-01-14 19:21:43 +01:00
Luis Ángel San Martín
e532fa4439 Use devicePixelRatioF instead of devicePixelRatio when rendering pages 2022-01-13 23:12:05 +01:00
Luis Ángel San Martín
c414761be7 Update CHANGELOG 2022-01-13 23:06:39 +01:00
Luis Ángel San Martín
fce1f163aa Check that double click is done using the left button before toggling full-screen mode 2022-01-13 23:06:24 +01:00
Luis Ángel San Martín
d20958c14f Make forward and backward mouse buttons turn pages 2022-01-13 23:05:49 +01:00
Luis Ángel San Martín
dcb7e6e0c6 Add settings to control the page turn behavior on scroll 2022-01-13 23:04:22 +01:00
Luis Ángel San Martín
a1e0340b3d Fix some warnings 2021-12-29 17:58:40 +01:00
Luis Ángel San Martín
d8f224645d Remove YACReader::SearchModifiers
They are no longer used
2021-12-29 14:58:03 +01:00
Luis Ángel San Martín
1cf4ef97ea
Merge pull request #215 from vedgy/exit-search-mode-before-creating-folder
Library: exit search mode before creating a folder
2021-12-29 12:48:42 +01:00
Igor Kushnir
ea2c90011a Library: exit search mode before creating a folder
Creating a folder in search mode selects it and makes the UI look
half-way between Normal and Searching navigation statuses.

An alternative fix is to disable addFolderAction in search mode. But
this is more difficult to implement and inconsistent with the other
always-enabled folder and reading list actions.
2021-12-29 11:02:01 +01:00
Igor Kushnir
40ca07f8f8 Extract YACReaderNavigationController::exitSearchMode() 2021-12-29 11:01:35 +01:00
Luis Ángel San Martín
3a60d89f06
Merge pull request #239 from vedgy/concurrent-queue-fixes
Test, fix, improve, optimize ConcurrentQueue
2021-12-29 10:53:17 +01:00
Luis Ángel San Martín
e2cc1064fe Run tests in qt6 too 2021-12-29 09:36:44 +01:00
Igor Kushnir
d013abedc1 Azure: run tests as a step of each platform's job 2021-12-29 09:36:44 +01:00
Igor Kushnir
05b384ed6d Make ConcurrentQueue::waitAll() const
This member function does not affect the logical state of the class.
Making std::condition_variable and std::mutex data members mutable is
idiomatic as these classes are thread-safe synchronization primitives.
2021-12-29 09:36:44 +01:00
Igor Kushnir
72c78d0164 ConcurrentQueue: remove redundant checks
The C++ standard specifies `std::condition_variable::wait(lock, pred)`
as equivalent to `while (!pred()) wait(lock);`. It is implemented
exactly so in libstdc++, libc++ and MSVC.
2021-12-29 09:36:44 +01:00
Igor Kushnir
4cb542c8cc Remove ConcurrentQueue::joinAll()
This function is called only from ~ConcurrentQueue(). joinAll() is not
thread-safe and it cannot be called earlier without introducing a null
state. Moving the function's implementation into the definition of
~ConcurrentQueue() makes the code clearer. Removing joinAll() also
allows to establish and document invariants for two data members.

Assert consistency between jobsLeft and _queue in ~ConcurrentQueue().
2021-12-29 09:36:44 +01:00
Igor Kushnir
61cd245037 Document ConcurrentQueue and de-inline its implementation
ConcurrentQueue is currently used only by two classes and a test, but
modifying concurrent_queue.h requires recompiling 30 source files. None
of the member functions is so lightweight as to make it worth inlining.

An alternative to `@note ConcurrentQueue is unable to execute jobs if
@p threadCount == 0.` is `assert(threadCount != 0);`. But this would
force classes that contain a ConcurrentQueue data member to always start
a thread, even if they detect at runtime that they are never going to
enqueue a job.

Add Job type alias to avoid repeating the type.

Use default member initializers instead of the member initializer list
to make it clear [to the reader of the header] that no data member is
left uninitialized.
2021-12-29 09:36:44 +01:00
Igor Kushnir
2655613543 ConcurrentQueue: simplify the constructor implementation
* threadCount argument: int => std::size_t to avoid implicit casting;
* eliminate temporary empty std::thread objects;
* replace a trivial lambda with a function pointer and its argument;
* get rid of the unused dedicated loop counter.
2021-12-29 09:36:44 +01:00
Igor Kushnir
c333fbc7d0 ConcurrentQueueTest::randomCalls(): test dominant enqueue()
Add another data column to randomCalls() - boostEnqueueOperationWeight -
that determines whether or not enqueue() operation's weight/probability
is much greater than the other operations' weights. The new column
allows to retain the existing much-closer-to-equal weights, which test
other aspects of ConcurrentQueue and have detected bugs already.

Reduce the number of thread count data rows to offset the test duration
increase somewhat.
2021-12-29 09:36:44 +01:00
Igor Kushnir
d026050d49 ConcurrentQueue::jobsLeft: int => std::size_t
This data member's type can be unsigned because its value is never
negative now. Matching std::queue::size_type allows to improve type
safety, get rid of a static_cast and remove two assertions. The only
downside is a slight increase of sizeof(ConcurrentQueue).
2021-12-29 09:36:44 +01:00
Igor Kushnir
d8a6b7f432 ConcurrentQueue: std::move jobs
Moving a std::function can be faster than copying it. Correcting these
normally minor inefficiencies is important here because they occur under
a mutex lock.
2021-12-29 09:36:44 +01:00
Igor Kushnir
d869e1230b ConcurrentQueue::enqueue: increment jobsLeft before adding a job
ConcurrentQueueTest::randomCalls() built in Debug mode often crashes
when concurrent_queue_test.cpp is modified and ConcurrentQueueTest is
launched from Qt Creator so that the test is built and immediately run.
The crash is an assertion failure that occurs most of the time under
the described circumstances at different test data rows:
void YACReader::ConcurrentQueue::finalizeJobs(int): Assertion `jobsLeft >= count' failed.

The assertion fails because ConcurrentQueue::enqueue() adds a job into
the queue first and then increments jobsLeft. If the job is immediately
picked up and executed very fast, ConcurrentQueue::nextJob() can try to
finalize it before enqueue() increments jobsLeft.

Simply reordering the modifications of jobsLeft and _queue in enqueue()
ensures that jobsLeft is always non-negative and eliminates the
assertion failures. Note that ConcurrentQueue::finalizeJobs() is the
only other function that modifies (decreases) jobsLeft. finalizeJobs()
is always called *after* the queue's size is reduced. So the following
invariant is now maintained at all times and documented:
jobsLeft >= _queue.size().
2021-12-29 09:36:44 +01:00
Igor Kushnir
57eb8d0171 ConcurrentQueue::cancelPending: notify _waitVar
This allows to call cancelPending() and waitAll() concurrently with no
additional synchronization. While calling these two functions
concurrently may not be useful often, supporting it costs little in
terms of performance and code complexity. Furthermore, a completely
thread-safe class is easier to document and use correctly.

Optimize job finalization by notifying _waitVar only if jobsLeft is
reduced to 0.

Optimize cancelPending() by not locking jobsLeftMutex when no job is
canceled (if the queue is empty when this function is called).

Add assertions that verify invariants.

The output of ConcurrentQueueTest::randomCalls() reflects the fact that
a caller of waitAll() can be blocked indefinitely when another thread
cancels all queued jobs while no job is being executed. The test output
snippet below omits repetitive information: the
"QINFO  : ConcurrentQueueTest::randomCalls(queue{1}; 2 user thread(s))"
prefix of each line, current thread id and the common hh:mm:ss current
time part (leaving only the millisecond part). Note that thread #1
begins waiting at the 1st line of the snippet. "=> -8" means that total
equals -8 and thus ConcurrentQueue::jobsLeft equals 8. Thread #0 then
cancels all 8 queued jobs, then enqueues and cancels 3 jobs twice, then
momentarily waits 3 times. #1 is not waked until #0 enqueues 8 more jobs
and starts waiting for them too. Once these new 8 jobs are completed,
both #0 and #1 end waiting.

 1. [ms] 311 | #1 begin waiting for 1 thread => -8
 2. [ms] 311 | #0 enqueuing complete.
 3. [ms] 311 | #0 canceled 8 jobs => -8
 4. [ms] 311 | #0 enqueuing 3 jobs...
 5. [ms] 312 | #0 enqueuing complete.
 6. [ms] 312 | #0 canceled 3 jobs => -3
 7. [ms] 312 | #0 enqueuing 3 jobs...
 8. [ms] 312 | #0 enqueuing complete.
 9. [ms] 312 | #0 canceled 3 jobs => -3
10. [ms] 312 | #0 begin waiting for 1 thread => 0
11. [ms] 312 | #0 end waiting for 1 thread => 0
12. [ms] 312 | #0 begin waiting for 1 thread => 0
13. [ms] 312 | #0 end waiting for 1 thread => 0
14. [ms] 312 | #0 begin waiting for 1 thread => 0
15. [ms] 312 | #0 end waiting for 1 thread => 0
16. [ms] 312 | #0 canceled 0 jobs => 0
17. [ms] 312 | #0 canceled 0 jobs => 0
18. [ms] 312 | #0 enqueuing 3 jobs...
19. [ms] 312 | #0 enqueuing complete.
20. [ms] 312 | #0 enqueuing 3 jobs...
21. [ms] 312 | #0 enqueuing complete.
22. [ms] 312 | #0 enqueuing 2 jobs...
23. [ms] 312 | #0 enqueuing complete.
24. [ms] 312 | #0 begin waiting for 1 thread => -8
25. [ms] 312 | [0.1] sleep 0.003 ms...
26. [ms] 312 | [0.1] +1 => -7
27. [ms] 312 | [0.2] sleep 0.003 ms...
28. [ms] 312 | [0.2] +1 => -6
29. [ms] 312 | [0.3] sleep 0.003 ms...
30. [ms] 312 | [0.3] +1 => -5
31. [ms] 312 | [0.1] sleep 0 ms...
32. [ms] 313 | [0.1] +1 => -4
33. [ms] 313 | [0.2] sleep 0.005 ms...
34. [ms] 313 | [0.2] +1 => -3
35. [ms] 313 | [0.3] sleep 0 ms...
36. [ms] 313 | [0.3] +1 => -2
37. [ms] 313 | [0.1] sleep 0.001 ms...
38. [ms] 313 | [0.1] +1 => -1
39. [ms] 313 | [0.2] sleep 0.001 ms...
40. [ms] 313 | [0.2] +1 => 0
41. [ms] 313 | #0 end waiting for 1 thread => 0
42. [ms] 313 | #1 end waiting for 1 thread => 0
2021-12-29 09:36:44 +01:00
Igor Kushnir
b514ba1270 tests: add ConcurrentQueueTest::randomCalls()
The new test is a randomized stress test. It consistently passes right
now. The test's detailed output can be analyzed to reveal anomalies and
bugs. The test can catch various bugs that future changes to
ConcurrentQueue's code may introduce.
2021-12-29 09:36:44 +01:00
Igor Kushnir
e8b5f42e75 ConcurrentQueue::nextJob: notify all _waitVar threads
Replace _waitVar.notify_one() with _waitVar.notify_all(). This was the
only hurdle to documenting ConcurrentQueue::waitAll() as thread-safe.

ConcurrentQueueTest::waitAllFromMultipleThreads() passes instead of
hanging now.
2021-12-29 09:36:44 +01:00
Igor Kushnir
2cbcaaa391 tests: add ConcurrentQueueTest::waitAllFromMultipleThreads()
This new test hangs because ConcurrentQueue::nextJob() unblocks only
one of the threads that wait for _waitVar.
2021-12-29 09:36:44 +01:00
Igor Kushnir
34b0698d02 Make ConcurrentQueueTest::cancelPending1UserThread() non-flaky
ConcurrentQueueTest::cancelPending1UserThread() often fails when
ConcurrentQueueTest is launched from Qt Creator immediately after
switching between Debug and Release YACReader build configurations.
The CPU busyness must be affecting the thread scheduling timing, which
breaks the test's timing assumptions in this case.

Use the return value of ConcurrentQueue::cancelPending() instead of
relying on the timing of thread scheduling to determine the number of
canceled jobs.
2021-12-29 09:36:44 +01:00
Igor Kushnir
b43e3383d5 ConcurrentQueue::cancelPending: return the number of canceled jobs
The return value can be used to make
ConcurrentQueueTest::cancelPending1UserThread() non-flaky. It may also
be useful to non-testing code.

Improve the performance of cancelPending() by locking the two mutexes
separately and minimizing the locking time.
2021-12-29 09:36:44 +01:00
Igor Kushnir
a72fdb9ca2 ConcurrentQueue::cancelPending: don't reset jobsLeft to 0
Worker threads may well be executing jobs while this function is being
called. If ConcurrentQueue::waitAll() is called soon enough after
cancelPending(), the worker threads may still be running, but waitAll()
would return immediately as jobsLeft would be nonpositive.

Subtracting _queue.size() from jobsLeft sets this variable to the number
of worker threads that are executing jobs at the moment.

ConcurrentQueueTest::cancelPending1UserThread() passes most of the time
now. But it still fails occasionally because it depends on the timing of
thread scheduling, which is unreliable.
2021-12-29 09:36:44 +01:00
Igor Kushnir
4bbd16c3b3 tests: add ConcurrentQueueTest::cancelPending1UserThread()
This new test consistently fails because of a bug in
ConcurrentQueue::cancelPending() described in the following comment:
https://github.com/YACReader/yacreader/issues/201#issuecomment-774987383
2021-12-29 09:36:44 +01:00
Igor Kushnir
228fe1284e Fix a typo in ConcurrentQueue::cancelPending function name 2021-12-29 09:36:44 +01:00
Igor Kushnir
ec938651c4 tests: add the first Qt Test - ConcurrentQueueTest
Place common Qt Test qmake code into tests/qt_test.pri.

Build tests as part of top-level YACReader project unless no_tests
CONFIG option is set. This way the tests are built by default during
development. Packagers can skip building tests by running
`qmake "CONFIG+=no_tests"`.

Both ConcurrentQueueTest::singleUserThread() and
ConcurrentQueueTest::multipleUserThreads() pass. Evidently
ConcurrentQueue::enqueue() can be safely called from multiple threads on
the same ConcurrentQueue object with no additional synchronization. Once
each thread enqueues all its jobs, one thread can safely call waitAll().
2021-12-29 09:36:44 +01:00
Igor Kushnir
b0b0849cbc Extract DEFINES += NOMINMAX into common config.pri 2021-12-29 09:36:44 +01:00
Luis Ángel San Martín
ca7be7cc2d Fix download file name 2021-12-29 09:13:54 +01:00
Luis Ángel San Martín
c10f181054 Use mirror for 7z and p7zip src deps
For some reason downloading from sourceforge fails a lot and makes CI unusable.
2021-12-29 08:49:49 +01:00
Luis Ángel San Martín
040883a107 Fix static assert when compiling with Qt 6.2.2
QtConcurrent::run doesn't like that reference anymore
2021-12-28 17:48:35 +01:00
Luis Ángel San Martín
97d21a5f27 Install required additional qt6 modules 2021-12-28 16:40:30 +01:00
Luis Ángel San Martín
ef5b8136ae Add a build job for qt6 to ensure that no regressions are introduced that will break qt6 compilation 2021-12-28 15:56:50 +01:00
Luis Ángel San Martín
e822e19703 Update CHANGELOG 2021-12-27 16:44:21 +01:00