Compare commits

..

123 commits

Author SHA1 Message Date
Anton Thomasson
38975d1f64 Fix pkgconfig includes 2023-03-04 13:27:38 +01:00
Anton Thomasson
53f458d71a Fix exec in .desktop 2023-03-04 12:58:51 +01:00
Anton Thomasson
f138f0b89d Add missing files 2023-03-04 12:41:12 +01:00
Anton Thomasson
1d71f3477a Enforce legacy compression for rpm 2023-03-04 12:20:13 +01:00
Anton Thomasson
2d0d61f3e5 Update german translation.
Partially based on #77
2023-03-04 12:12:02 +01:00
Anton Thomasson
1905cec933 Improve integer input hints/labels 2023-02-25 16:15:45 +01:00
Anton Thomasson
912bc36dad Remove empty translations 2023-02-25 14:51:45 +01:00
Anton Thomasson
317a864ff6 Add net.attah.seaprint DBus methods 2023-02-20 20:45:20 +01:00
Anton Thomasson
d473d23d55 Support getting text data from dbus in PlatformData 2023-02-19 21:14:09 +01:00
Anton Thomasson
29c5bbf62b Make it DBusActivatable 2023-02-19 17:08:46 +01:00
Anton Thomasson
e4038db1b0 Update copyright year 2023-01-31 21:34:20 +01:00
Anton Thomasson
fce00048ef Adjust label text size 2023-01-29 15:47:38 +01:00
Anton Thomasson
62bcecccbe Add basic label printer support 2023-01-29 13:47:43 +01:00
Anton Thomasson
d3bc294f0a Sync submodules 2023-01-16 20:35:43 +01:00
Anton Thomasson
5d5e86dc6b Fix image-to-pdf in flowchart, break apart pdf2printable and ppm2pwg boxes 2022-12-17 20:51:01 +01:00
Anton Thomasson
3045244c71 Update README.md 2022-12-17 17:46:08 +01:00
Anton Thomasson
3aad66c714 Adjust format flowchart 2022-12-17 17:35:17 +01:00
Anton Thomasson
31bfd08fae Add format flowchart 2022-12-17 17:19:19 +01:00
Anton Thomasson
6f4d602456 Fix advanced page range input 2022-11-17 20:21:43 +01:00
Anton Thomasson
3d8ade0bef Sync submodules 2022-10-22 13:24:22 +02:00
Anton Thomasson
7a144471e3 Fix German translation 2022-10-22 13:22:36 +02:00
wheelseal
5433d81764 Update harbour-seaprint-de.ts
Update harbour-seaprint-de.ts

Update harbour-seaprint-de.ts

Update harbour-seaprint-de.ts

Update harbour-seaprint-de.ts

Update harbour-seaprint-de.ts

Update harbour-seaprint-de.ts

Update harbour-seaprint-de.ts

Update harbour-seaprint-de.ts

Update harbour-seaprint-de.ts

Update harbour-seaprint-de.ts

Update harbour-seaprint-de.ts

Update harbour-seaprint-de.ts

Is Line 5146-5149 needed?

Update the German Translation

Line 1381 + 1386: I don´t know this format, sorry.

missing changes of update German translation

You are right. My two different translations of Hagaki was a bit confusing. But so many lines... so I forgot that I had translated it before.
2022-10-22 13:11:32 +02:00
Anton Thomasson
a51061df7c Hide identify-menu if empty 2022-08-27 11:17:06 +02:00
Anton Thomasson
741bdd7724 Sync submodules 2022-08-19 20:25:24 +02:00
Anton Thomasson
f9b740c94b Ensure increasing page range list 2022-08-19 18:32:06 +02:00
Anton Thomasson
371c06b430 Sync submodules 2022-08-19 18:30:29 +02:00
Anton Thomasson
38d5a95982 Reset position for decoded answer when passed as failure response 2022-08-13 21:02:00 +02:00
Anton Thomasson
ac84fe079e Use PrintParameters for paper sizes 2022-08-13 20:04:10 +02:00
Anton Thomasson
53ca370f92 Add advanced page ranges (list of ranges) 2022-08-13 16:35:03 +02:00
Anton Thomasson
3abd1e103f Enable encoding 1-set-of 2022-08-13 16:34:00 +02:00
Anton Thomasson
8712ee6eea Remove unneccessary function argument 2022-08-13 12:10:40 +02:00
Anton Thomasson
9287da53aa Move some raster compensation to where it belongs 2022-08-01 20:30:22 +02:00
Anton Thomasson
6a06fa5ffd Add support for client-side copies 2022-07-31 22:08:54 +02:00
Anton Thomasson
b7aeb097a3 Optimize raster logic a bit 2022-07-31 19:47:45 +02:00
Anton Thomasson
b70be18897 Sync submodules 2022-07-29 14:07:24 +02:00
Anton Thomasson
15cae28413 Improve PrinterWorker threading
Let PrinterWorker own its thread.
Don't access IppPrinter from PrinterWorker.
2022-07-20 15:18:49 +02:00
Anton Thomasson
5f71f2dd64 Add optional job-id to prioritized/sorted operation attributes
Some printers are really picky...
2022-07-18 22:27:39 +02:00
Anton Thomasson
51c90e6443 Add two-stage create/send print job support 2022-07-17 17:28:03 +02:00
Anton Thomasson
d037600d0f Don't use chunked encoding when there is no document 2022-07-17 17:28:03 +02:00
Anton Thomasson
d094db4d89 Update translation credits 2022-07-17 17:07:41 +02:00
archon
515d3944e7
Update harbour-seaprint-pl.ts (#69)
* Update harbour-seaprint-pl.ts

Improved polish translation - I have filled the blanks.
2022-07-17 17:05:16 +02:00
Anton Thomasson
31cdaf0939 Mark some empty translations unfinished 2022-07-13 10:11:02 +02:00
Anton Thomasson
2cd0bb2812 Fix binary installation directive 2022-07-12 14:57:23 +02:00
Anton Thomasson
445cc19b10 Improve rpm spec 2022-07-12 10:47:39 +02:00
Anton Thomasson
56395707fa Unbork translation template 2022-07-10 16:44:25 +02:00
Anton Thomasson
aaae57b113 Fix boolean encoding 2022-07-10 16:39:19 +02:00
Anton Thomasson
da923e3a32 Make await an explicit step on CurlRequester 2022-07-10 13:11:20 +02:00
Anton Thomasson
071c39b6f3 Move operation to IppMsg constructor 2022-07-10 11:36:21 +02:00
Anton Thomasson
04830863ab Simplify IppPrinter->PrinterWorker interface 2022-07-10 10:29:56 +02:00
Anton Thomasson
95ef563230 Use QImageReader so images are loaded in correct orientation 2022-07-06 20:31:09 +02:00
Anton Thomasson
8ea54769c5 Sync submodules 2022-07-05 21:05:34 +02:00
Anton Thomasson
f6ec27b423 Sync submodules 2022-06-19 10:50:35 +02:00
Anton Thomasson
0874122bda Ensure no decimals in supply level rendering 2022-06-13 20:45:46 +02:00
Anton Thomasson
813e1edb6a Fix plaintext 2022-06-10 19:40:51 +02:00
Anton Thomasson
c30e7d3548 Remove DockedPanel 2022-06-06 09:47:17 +02:00
Anton Thomasson
fb40a89858 Don't remove icons from fake-file printers
...just don't try to load them
2022-06-04 17:42:18 +02:00
Anton Thomasson
64a7b0120f Guard against 0-length values 2022-06-04 14:44:26 +02:00
Anton Thomasson
8c6344eef8 Enable raw/binary fake-printers 2022-06-04 14:43:53 +02:00
Anton Thomasson
9ff8775fab Sync sybmodules 2022-06-01 17:58:55 +02:00
Anton Thomasson
6c7e09fdb5 Add SVG support 2022-05-30 19:35:18 +02:00
Anton Thomasson
1e6be94965 Handle 0 as marker high level 2022-05-27 14:28:32 +02:00
Anton Thomasson
342a1a82f0 Improve printer discovery logic 2022-05-27 14:19:56 +02:00
Anton Thomasson
58619bd63a Improve likelyhood of name resolution 2022-05-26 14:42:01 +02:00
Anton Thomasson
a7d181a867 Sync submodules 2022-05-26 13:13:34 +02:00
Anton Thomasson
18f0c66dba Give better feedback when there is no way to convert 2022-05-26 12:28:29 +02:00
Anton Thomasson
ab96dc547f Add Reverse-BMP format 2022-05-26 11:57:16 +02:00
Anton Thomasson
6ba065b4b1 Drop use of qt foreach 2022-05-22 11:00:32 +02:00
Anton Thomasson
ba2a826b56 Fix potential icon request loop 2022-05-19 20:49:57 +02:00
Anton Thomasson
21b3819a86 Pull in ppm2pwg optimizations 2022-05-16 21:59:19 +02:00
Anton Thomasson
08b66ed711 Stop resizing images when transferred as such 2022-05-16 21:52:00 +02:00
Anton Thomasson
ff2eae04cf Add Identify-Printer 2022-05-16 20:29:53 +02:00
Anton Thomasson
b4aaa85a2d Add option for restricting non-printer connections 2022-05-15 21:21:32 +02:00
Anton Thomasson
f30f40683e Update README for plaintext handling 2022-05-03 21:19:04 +02:00
Anton Thomasson
f84d05e514 Fix menu lock bug with no choices 2022-05-02 21:30:02 +02:00
Anton Thomasson
dd19333c81 Support printing plaintext as plaintext
It will be converted ro \r\n linebraks, but still has lowest prio
for printing plaintext input due to unreliable rendering.
2022-05-02 21:20:26 +02:00
Anton Thomasson
b1cd524b4c Make media, media-type and printer-state-reasons translatable 2022-04-25 15:42:17 +02:00
Anton Thomasson
0dd09a2707 Add RemovableMedia permission 2022-04-17 12:11:26 +02:00
Anton Thomasson
6e2ded84e2 Fix plaintext printing >2 pages 2022-04-09 13:30:41 +02:00
Anton Thomasson
5799985c92 Fix status message for PDF 2022-04-09 13:30:23 +02:00
Anton Thomasson
cecc038a6d Fix more broken stuff after restructuring 2022-03-26 18:35:22 +01:00
Anton Thomasson
993eb75cf7 Fix printing on non-A4 after restructuring 2022-03-23 20:34:38 +01:00
attah
ea39f0285b
Update README.md 2022-03-19 20:06:34 +01:00
Anton Thomasson
438628598f Mention SailJail in Calligra instructions 2022-03-19 19:57:54 +01:00
Anton Thomasson
09ceb4f0ec Use PrintParameters for conversion settings 2022-03-19 18:51:13 +01:00
Anton Thomasson
e16bfbe7c6 Don't send margins when effecting them locally 2022-03-19 17:55:55 +01:00
Anton Thomasson
ef3b53abdb Add re-encoding of jpegs to baseline profile 2022-03-06 17:16:07 +01:00
Anton Thomasson
bb6fa7926d Fix syntax in harbour-seaprint-de.ts 2022-03-06 16:49:03 +01:00
Anton Thomasson
40da0790e2 Remove unneccessary SvgProvider 2022-02-21 18:14:04 +01:00
Anton Thomasson
0312a367f4 Sync submodules 2022-02-20 22:02:19 +01:00
Anton Thomasson
71b2986a37 Spring cleaning 2022-02-20 14:13:11 +01:00
Anton Thomasson
3be30e58e6 Squelch a warning 2022-02-19 18:43:44 +01:00
Anton Thomasson
444cf09ae0 Sync submodules 2022-02-19 18:36:43 +01:00
Anton Thomasson
0efd96c072 Make the icon an attribute on the printer object
This allows retrying based on actual size and using cURL for getting
the icon to avoid old Qt network code.
2022-02-19 17:48:51 +01:00
Anton Thomasson
d8267db9fe Update transaltion credits 2022-02-17 19:33:08 +01:00
Patrick Hervieux
4537f32005
Update French translation (#65)
Co-authored-by: Patrick Hervieux <4703153-pherjung@users.noreply.gitlab.com>
2022-02-17 19:27:58 +01:00
Anton Thomasson
9ba3f2daf1 Sync submodules 2022-02-13 15:20:59 +01:00
wheelseal
4f06a8e85e
Update German translation (#64)
- Update German translation
- Fix two write errors
2022-02-01 19:26:32 +01:00
Anton Thomasson
595a253693 Hook up debug log switch to raster verbose mode 2022-01-23 17:36:47 +01:00
Anton Thomasson
23fe3741f9 Sync submodules 2022-01-23 12:56:26 +01:00
Anton Thomasson
8ae48b83e6 Remove attributes the correct way 2022-01-23 12:52:34 +01:00
Anton Thomasson
afb8d65ea8 Clean up logs a bit 2022-01-19 20:18:00 +01:00
Anton Thomasson
390e5dd16d Make debug log configurable 2022-01-19 20:03:19 +01:00
Anton Thomasson
40e7f32846 Let curl debug follow qt debug settings 2022-01-18 20:41:08 +01:00
Anton Thomasson
263090a518 Update translation credits 2022-01-18 19:39:11 +01:00
JSEHV
3bcba3b8d7
Update harbour-seaprint-es.ts (#63)
* Update harbour-seaprint-es.ts
2022-01-18 19:32:51 +01:00
Anton Thomasson
8843275fde Remove redundant deleteLater 2022-01-17 21:32:12 +01:00
Anton Thomasson
8b4c2f373b Plug a leak 2022-01-14 22:49:38 +01:00
Anton Thomasson
2ab6a7d979 Resolve string uris 2022-01-14 22:44:55 +01:00
Anton Thomasson
0684e677ec Get printer strings 2022-01-14 22:14:06 +01:00
Anton Thomasson
6c4e48d69c restructure curlrequester 2022-01-14 21:24:07 +01:00
Anton Thomasson
47cde78edc Rearrange details page, add performance data 2022-01-13 21:10:47 +01:00
Anton Thomasson
cd90064f9a Highligt low supplies 2022-01-13 21:09:25 +01:00
Allan Nordhøy
9da439e463
"en" language specified for base language file (#61) 2022-01-13 20:05:30 +01:00
Anton Thomasson
6095694d92 Fix newline at end of file
(will get automatic changes otherwise)
2022-01-11 20:08:17 +01:00
JSEHV
4c0a7ed9fd
Update harbour-seaprint-nl.ts (#57)
* Update harbour-seaprint-nl.ts
2022-01-11 19:49:39 +01:00
Anton Thomasson
02be96f090 Update ppm2pwg, for sanity 2022-01-08 20:29:39 +01:00
Anton Thomasson
d6db1ff5e2 Naming and cleanup of worker 2022-01-07 20:36:21 +01:00
Anton Thomasson
c93d4a019f Add support for presentations 2022-01-07 20:05:21 +01:00
Anton Thomasson
71dd1452bc Update ppm2pwg (portrait centering fix) 2022-01-07 20:05:01 +01:00
Anton Thomasson
4e5967e36d Add translation credits, minor fixes. 2022-01-06 12:23:02 +01:00
attah
6ee912680e
Merge pull request #56 from phklrz/patch-1
Update harbour-seaprint-fr.ts
2022-01-06 12:16:37 +01:00
phklrz
b6288ca1ee
Update harbour-seaprint-fr.ts 2022-01-06 11:48:58 +01:00
phklrz
d20c115747
Update harbour-seaprint-fr.ts 2022-01-05 17:43:44 +01:00
70 changed files with 38191 additions and 4414 deletions

View file

@ -11,7 +11,7 @@ A minority of printers support only PCL and/or their own proprietary formats, th
## Format support
For PDFs or plaintext, your printer needs to support one of the following formats:
For PDFs, your printer needs to support one of the following formats:
* PDF
* Postscript
* PWG-raster
@ -19,6 +19,9 @@ For PDFs or plaintext, your printer needs to support one of the following format
For Postscript, your printer needs to support Postscript natively. (SeaPrint just sends it as-is)
For Plaintext, SeaPrint can render it to PDF internally and then the requirements follow those of PDF.
You can also send Plaintext to supporting printers and leave the rendering up to the printer (SeaPrint will strip leading and trailing Form Feed characters and ensure CR+LF newlines, but not do any reflow or similar). Since rendering will differ between printers, this has lowest priority for automatic format selection.
For printing "Office" files, like .doc(x) and odt, you need to install
`calligraconverter` (from the `calligra` package in Sailfish OS 4.2 onwards).
Then the printer support follows that of PDF.
@ -31,8 +34,18 @@ For images, your printer needs to support one of the following formats:
* PDF
* Postscript
For printing JPEG images, transferring the unaltered image has priority and the printer does the scaling.
Other image formats are lossless and SeaPrint does scale-and-rotate to fit, so JPEG has lowest prio there.
When printing images the result may depend on how they are transferred.
If a document-oriented format like PDF/Postscript/PWG/URF is used SeaPrint does scale-and-rotate to fit, otherwise (when transferred as PNG or JPEG) that is left up to the printer. Only when printing images *as images* can the scaling setting be used.
For printing JPEG images, transferring them as JPEG has highest priority.
JPEGs are then (losslessly) re-encoded to the baseline encoding profile to work with all printers (since SeaPrint 1.1).
For other image formats JPEG has lowest priority as transfer format since it is lossy.
![Format flowchart](doc/pipeline.svg)
Flowchart made with [GoAT](https://github.com/blampe/goat).
pdf2printable, ppm2pwg and baselinify were made especially for SeaPrint and can be found [here](https://github.com/attah/ppm2pwg).
Printers with any of these IPP certifications and derivative standards should likely be supported:

27
doc/pipeline.goat Normal file
View file

@ -0,0 +1,27 @@
Normal flow:
.---------------.
PDF -------------------------------------------->| +----------------------> PDF 1.5
.-----------------. | |
Plaintext ---->| QTextDocument +------------------>| pdf2printable +----------------------> Postscript 2
'-----------------' | |
DOC/PPT .-------------------. | (Poppler+Cairo) | .---------.
ODT/ODP ---->| Calligraconverter +---------------->| | | |
| (optional) | | +---->| +----> PWG-Raster
'-------------------' .--------. | | | ppm2pwg |
Images --------------------------->| +---->| | | +----> URF-Raster
| | '---------------' | |
| QImage +---------------------------->| |
.--------------. | | '---------'
SVG ---->| QSvgRenderer +---->| +----------------------------------------------> PNG, JPEG,
'--------------' '--------' Reverse BMP
If target format is the same:
Postscript --------------------------------------------------------------------------------------> Postscript
.-----------------------.
Plaintext ---->| ensure CR+LF newlines +------------------------------------------------------> Plaintext
'-----------------------'
PNG --------------------------------------------------------------------------------------> PNG
.------------.
JPEG ---->| baselinify +-----------------------------------------------------------------> JPEG
'------------' (Baseline profile)

444
doc/pipeline.svg Normal file
View file

@ -0,0 +1,444 @@
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='441' width='976' font-family='Menlo,Lucida Console,monospace'>
<style type="text/css">
svg {
color: #000000;
}
@media (prefers-color-scheme: dark) {
svg {
color: #FFFFFF;
}
}
</style>
<g transform='translate(8,16)'>
<path d='M 496,16 L 608,16' fill='none' stroke='currentColor'></path>
<path d='M 120,32 L 472,32' fill='none' stroke='currentColor'></path>
<path d='M 624,32 L 808,32' fill='none' stroke='currentColor'></path>
<path d='M 176,48 L 304,48' fill='none' stroke='currentColor'></path>
<path d='M 120,64 L 152,64' fill='none' stroke='currentColor'></path>
<path d='M 320,64 L 472,64' fill='none' stroke='currentColor'></path>
<path d='M 624,64 L 808,64' fill='none' stroke='currentColor'></path>
<path d='M 176,80 L 304,80' fill='none' stroke='currentColor'></path>
<path d='M 176,96 L 320,96' fill='none' stroke='currentColor'></path>
<path d='M 688,96 L 752,96' fill='none' stroke='currentColor'></path>
<path d='M 120,112 L 152,112' fill='none' stroke='currentColor'></path>
<path d='M 336,112 L 472,112' fill='none' stroke='currentColor'></path>
<path d='M 624,128 L 664,128' fill='none' stroke='currentColor'></path>
<path d='M 768,128 L 808,128' fill='none' stroke='currentColor'></path>
<path d='M 176,144 L 320,144' fill='none' stroke='currentColor'></path>
<path d='M 360,144 L 416,144' fill='none' stroke='currentColor'></path>
<path d='M 120,160 L 336,160' fill='none' stroke='currentColor'></path>
<path d='M 432,160 L 472,160' fill='none' stroke='currentColor'></path>
<path d='M 768,160 L 808,160' fill='none' stroke='currentColor'></path>
<path d='M 496,176 L 608,176' fill='none' stroke='currentColor'></path>
<path d='M 432,192 L 664,192' fill='none' stroke='currentColor'></path>
<path d='M 176,208 L 280,208' fill='none' stroke='currentColor'></path>
<path d='M 688,208 L 752,208' fill='none' stroke='currentColor'></path>
<path d='M 120,224 L 152,224' fill='none' stroke='currentColor'></path>
<path d='M 296,224 L 336,224' fill='none' stroke='currentColor'></path>
<path d='M 432,224 L 808,224' fill='none' stroke='currentColor'></path>
<path d='M 176,240 L 280,240' fill='none' stroke='currentColor'></path>
<path d='M 360,240 L 416,240' fill='none' stroke='currentColor'></path>
<path d='M 120,304 L 808,304' fill='none' stroke='currentColor'></path>
<path d='M 176,320 L 352,320' fill='none' stroke='currentColor'></path>
<path d='M 120,336 L 152,336' fill='none' stroke='currentColor'></path>
<path d='M 368,336 L 808,336' fill='none' stroke='currentColor'></path>
<path d='M 176,352 L 352,352' fill='none' stroke='currentColor'></path>
<path d='M 120,368 L 808,368' fill='none' stroke='currentColor'></path>
<path d='M 176,384 L 264,384' fill='none' stroke='currentColor'></path>
<path d='M 120,400 L 152,400' fill='none' stroke='currentColor'></path>
<path d='M 280,400 L 808,400' fill='none' stroke='currentColor'></path>
<path d='M 176,416 L 264,416' fill='none' stroke='currentColor'></path>
<path d='M 160,112 L 160,128' fill='none' stroke='currentColor'></path>
<path d='M 336,112 L 336,128' fill='none' stroke='currentColor'></path>
<path d='M 344,160 L 344,224' fill='none' stroke='currentColor'></path>
<path d='M 432,160 L 432,192' fill='none' stroke='currentColor'></path>
<path d='M 432,192 L 432,224' fill='none' stroke='currentColor'></path>
<path d='M 480,32 L 480,160' fill='none' stroke='currentColor'></path>
<path d='M 624,32 L 624,64' fill='none' stroke='currentColor'></path>
<path d='M 624,64 L 624,128' fill='none' stroke='currentColor'></path>
<path d='M 624,128 L 624,160' fill='none' stroke='currentColor'></path>
<path d='M 672,112 L 672,192' fill='none' stroke='currentColor'></path>
<path d='M 768,112 L 768,128' fill='none' stroke='currentColor'></path>
<path d='M 768,128 L 768,160' fill='none' stroke='currentColor'></path>
<path d='M 768,160 L 768,192' fill='none' stroke='currentColor'></path>
<polygon points='160.000000,64.000000 148.000000,58.400002 148.000000,69.599998' fill='currentColor' transform='rotate(0.000000, 152.000000, 64.000000)'></polygon>
<polygon points='160.000000,112.000000 148.000000,106.400002 148.000000,117.599998' fill='currentColor' transform='rotate(0.000000, 152.000000, 112.000000)'></polygon>
<polygon points='160.000000,224.000000 148.000000,218.399994 148.000000,229.600006' fill='currentColor' transform='rotate(0.000000, 152.000000, 224.000000)'></polygon>
<polygon points='160.000000,336.000000 148.000000,330.399994 148.000000,341.600006' fill='currentColor' transform='rotate(0.000000, 152.000000, 336.000000)'></polygon>
<polygon points='160.000000,400.000000 148.000000,394.399994 148.000000,405.600006' fill='currentColor' transform='rotate(0.000000, 152.000000, 400.000000)'></polygon>
<polygon points='344.000000,160.000000 332.000000,154.399994 332.000000,165.600006' fill='currentColor' transform='rotate(0.000000, 336.000000, 160.000000)'></polygon>
<polygon points='344.000000,224.000000 332.000000,218.399994 332.000000,229.600006' fill='currentColor' transform='rotate(0.000000, 336.000000, 224.000000)'></polygon>
<polygon points='480.000000,32.000000 468.000000,26.400000 468.000000,37.599998' fill='currentColor' transform='rotate(0.000000, 472.000000, 32.000000)'></polygon>
<polygon points='480.000000,64.000000 468.000000,58.400002 468.000000,69.599998' fill='currentColor' transform='rotate(0.000000, 472.000000, 64.000000)'></polygon>
<polygon points='480.000000,112.000000 468.000000,106.400002 468.000000,117.599998' fill='currentColor' transform='rotate(0.000000, 472.000000, 112.000000)'></polygon>
<polygon points='480.000000,160.000000 468.000000,154.399994 468.000000,165.600006' fill='currentColor' transform='rotate(0.000000, 472.000000, 160.000000)'></polygon>
<polygon points='672.000000,128.000000 660.000000,122.400002 660.000000,133.600006' fill='currentColor' transform='rotate(0.000000, 664.000000, 128.000000)'></polygon>
<polygon points='672.000000,192.000000 660.000000,186.399994 660.000000,197.600006' fill='currentColor' transform='rotate(0.000000, 664.000000, 192.000000)'></polygon>
<polygon points='816.000000,32.000000 804.000000,26.400000 804.000000,37.599998' fill='currentColor' transform='rotate(0.000000, 808.000000, 32.000000)'></polygon>
<polygon points='816.000000,64.000000 804.000000,58.400002 804.000000,69.599998' fill='currentColor' transform='rotate(0.000000, 808.000000, 64.000000)'></polygon>
<polygon points='816.000000,128.000000 804.000000,122.400002 804.000000,133.600006' fill='currentColor' transform='rotate(0.000000, 808.000000, 128.000000)'></polygon>
<polygon points='816.000000,160.000000 804.000000,154.399994 804.000000,165.600006' fill='currentColor' transform='rotate(0.000000, 808.000000, 160.000000)'></polygon>
<polygon points='816.000000,224.000000 804.000000,218.399994 804.000000,229.600006' fill='currentColor' transform='rotate(0.000000, 808.000000, 224.000000)'></polygon>
<polygon points='816.000000,304.000000 804.000000,298.399994 804.000000,309.600006' fill='currentColor' transform='rotate(0.000000, 808.000000, 304.000000)'></polygon>
<polygon points='816.000000,336.000000 804.000000,330.399994 804.000000,341.600006' fill='currentColor' transform='rotate(0.000000, 808.000000, 336.000000)'></polygon>
<polygon points='816.000000,368.000000 804.000000,362.399994 804.000000,373.600006' fill='currentColor' transform='rotate(0.000000, 808.000000, 368.000000)'></polygon>
<polygon points='816.000000,400.000000 804.000000,394.399994 804.000000,405.600006' fill='currentColor' transform='rotate(0.000000, 808.000000, 400.000000)'></polygon>
<path d='M 496,16 A 16,16 0 0,0 480,32' fill='none' stroke='currentColor'></path>
<path d='M 608,16 A 16,16 0 0,1 624,32' fill='none' stroke='currentColor'></path>
<path d='M 176,48 A 16,16 0 0,0 160,64' fill='none' stroke='currentColor'></path>
<path d='M 304,48 A 16,16 0 0,1 320,64' fill='none' stroke='currentColor'></path>
<path d='M 160,64 A 16,16 0 0,0 176,80' fill='none' stroke='currentColor'></path>
<path d='M 320,64 A 16,16 0 0,1 304,80' fill='none' stroke='currentColor'></path>
<path d='M 176,96 A 16,16 0 0,0 160,112' fill='none' stroke='currentColor'></path>
<path d='M 320,96 A 16,16 0 0,1 336,112' fill='none' stroke='currentColor'></path>
<path d='M 688,96 A 16,16 0 0,0 672,112' fill='none' stroke='currentColor'></path>
<path d='M 752,96 A 16,16 0 0,1 768,112' fill='none' stroke='currentColor'></path>
<path d='M 160,128 A 16,16 0 0,0 176,144' fill='none' stroke='currentColor'></path>
<path d='M 336,128 A 16,16 0 0,1 320,144' fill='none' stroke='currentColor'></path>
<path d='M 360,144 A 16,16 0 0,0 344,160' fill='none' stroke='currentColor'></path>
<path d='M 416,144 A 16,16 0 0,1 432,160' fill='none' stroke='currentColor'></path>
<path d='M 480,160 A 16,16 0 0,0 496,176' fill='none' stroke='currentColor'></path>
<path d='M 624,160 A 16,16 0 0,1 608,176' fill='none' stroke='currentColor'></path>
<path d='M 176,208 A 16,16 0 0,0 160,224' fill='none' stroke='currentColor'></path>
<path d='M 280,208 A 16,16 0 0,1 296,224' fill='none' stroke='currentColor'></path>
<path d='M 672,192 A 16,16 0 0,0 688,208' fill='none' stroke='currentColor'></path>
<path d='M 768,192 A 16,16 0 0,1 752,208' fill='none' stroke='currentColor'></path>
<path d='M 160,224 A 16,16 0 0,0 176,240' fill='none' stroke='currentColor'></path>
<path d='M 296,224 A 16,16 0 0,1 280,240' fill='none' stroke='currentColor'></path>
<path d='M 344,224 A 16,16 0 0,0 360,240' fill='none' stroke='currentColor'></path>
<path d='M 432,224 A 16,16 0 0,1 416,240' fill='none' stroke='currentColor'></path>
<path d='M 176,320 A 16,16 0 0,0 160,336' fill='none' stroke='currentColor'></path>
<path d='M 352,320 A 16,16 0 0,1 368,336' fill='none' stroke='currentColor'></path>
<path d='M 160,336 A 16,16 0 0,0 176,352' fill='none' stroke='currentColor'></path>
<path d='M 368,336 A 16,16 0 0,1 352,352' fill='none' stroke='currentColor'></path>
<path d='M 176,384 A 16,16 0 0,0 160,400' fill='none' stroke='currentColor'></path>
<path d='M 264,384 A 16,16 0 0,1 280,400' fill='none' stroke='currentColor'></path>
<path d='M 160,400 A 16,16 0 0,0 176,416' fill='none' stroke='currentColor'></path>
<path d='M 280,400 A 16,16 0 0,1 264,416' fill='none' stroke='currentColor'></path>
<style>
text {
text-anchor: middle;
font-family: "Menlo","Lucida Console","monospace";
fill: currentColor;
font-size: 1em;
}
</style>
<text x='16' y='4'>N</text>
<text x='24' y='4'>o</text>
<text x='32' y='4'>r</text>
<text x='40' y='4'>m</text>
<text x='48' y='4'>a</text>
<text x='56' y='4'>l</text>
<text x='72' y='4'>f</text>
<text x='80' y='4'>l</text>
<text x='88' y='4'>o</text>
<text x='96' y='4'>w</text>
<text x='104' y='4'>:</text>
<text x='32' y='36'>P</text>
<text x='40' y='36'>D</text>
<text x='48' y='36'>F</text>
<text x='824' y='36'>P</text>
<text x='832' y='36'>D</text>
<text x='840' y='36'>F</text>
<text x='856' y='36'>1</text>
<text x='864' y='36'>.</text>
<text x='872' y='36'>5</text>
<text x='32' y='68'>P</text>
<text x='40' y='68'>l</text>
<text x='48' y='68'>a</text>
<text x='56' y='68'>i</text>
<text x='64' y='68'>n</text>
<text x='72' y='68'>t</text>
<text x='80' y='68'>e</text>
<text x='88' y='68'>x</text>
<text x='96' y='68'>t</text>
<text x='192' y='68'>Q</text>
<text x='200' y='68'>T</text>
<text x='208' y='68'>e</text>
<text x='216' y='68'>x</text>
<text x='224' y='68'>t</text>
<text x='232' y='68'>D</text>
<text x='240' y='68'>o</text>
<text x='248' y='68'>c</text>
<text x='256' y='68'>u</text>
<text x='264' y='68'>m</text>
<text x='272' y='68'>e</text>
<text x='280' y='68'>n</text>
<text x='288' y='68'>t</text>
<text x='504' y='68'>p</text>
<text x='512' y='68'>d</text>
<text x='520' y='68'>f</text>
<text x='528' y='68'>2</text>
<text x='536' y='68'>p</text>
<text x='544' y='68'>r</text>
<text x='552' y='68'>i</text>
<text x='560' y='68'>n</text>
<text x='568' y='68'>t</text>
<text x='576' y='68'>a</text>
<text x='584' y='68'>b</text>
<text x='592' y='68'>l</text>
<text x='600' y='68'>e</text>
<text x='824' y='68'>P</text>
<text x='832' y='68'>o</text>
<text x='840' y='68'>s</text>
<text x='848' y='68'>t</text>
<text x='856' y='68'>s</text>
<text x='864' y='68'>c</text>
<text x='872' y='68'>r</text>
<text x='880' y='68'>i</text>
<text x='888' y='68'>p</text>
<text x='896' y='68'>t</text>
<text x='912' y='68'>2</text>
<text x='32' y='100'>D</text>
<text x='40' y='100'>O</text>
<text x='48' y='100'>C</text>
<text x='56' y='100'>/</text>
<text x='64' y='100'>P</text>
<text x='72' y='100'>P</text>
<text x='80' y='100'>T</text>
<text x='496' y='100'>(</text>
<text x='504' y='100'>P</text>
<text x='512' y='100'>o</text>
<text x='520' y='100'>p</text>
<text x='528' y='100'>p</text>
<text x='536' y='100'>l</text>
<text x='544' y='100'>e</text>
<text x='552' y='100'>r</text>
<text x='560' y='100'>+</text>
<text x='568' y='100'>C</text>
<text x='576' y='100'>a</text>
<text x='584' y='100'>i</text>
<text x='592' y='100'>r</text>
<text x='600' y='100'>o</text>
<text x='608' y='100'>)</text>
<text x='32' y='116'>O</text>
<text x='40' y='116'>D</text>
<text x='48' y='116'>T</text>
<text x='56' y='116'>/</text>
<text x='64' y='116'>O</text>
<text x='72' y='116'>D</text>
<text x='80' y='116'>P</text>
<text x='184' y='116'>C</text>
<text x='192' y='116'>a</text>
<text x='200' y='116'>l</text>
<text x='208' y='116'>l</text>
<text x='216' y='116'>i</text>
<text x='224' y='116'>g</text>
<text x='232' y='116'>r</text>
<text x='240' y='116'>a</text>
<text x='248' y='116'>c</text>
<text x='256' y='116'>o</text>
<text x='264' y='116'>n</text>
<text x='272' y='116'>v</text>
<text x='280' y='116'>e</text>
<text x='288' y='116'>r</text>
<text x='296' y='116'>t</text>
<text x='304' y='116'>e</text>
<text x='312' y='116'>r</text>
<text x='208' y='132'>(</text>
<text x='216' y='132'>o</text>
<text x='224' y='132'>p</text>
<text x='232' y='132'>t</text>
<text x='240' y='132'>i</text>
<text x='248' y='132'>o</text>
<text x='256' y='132'>n</text>
<text x='264' y='132'>a</text>
<text x='272' y='132'>l</text>
<text x='280' y='132'>)</text>
<text x='824' y='132'>P</text>
<text x='832' y='132'>W</text>
<text x='840' y='132'>G</text>
<text x='848' y='132'>-</text>
<text x='856' y='132'>R</text>
<text x='864' y='132'>a</text>
<text x='872' y='132'>s</text>
<text x='880' y='132'>t</text>
<text x='888' y='132'>e</text>
<text x='896' y='132'>r</text>
<text x='696' y='148'>p</text>
<text x='704' y='148'>p</text>
<text x='712' y='148'>m</text>
<text x='720' y='148'>2</text>
<text x='728' y='148'>p</text>
<text x='736' y='148'>w</text>
<text x='744' y='148'>g</text>
<text x='32' y='164'>I</text>
<text x='40' y='164'>m</text>
<text x='48' y='164'>a</text>
<text x='56' y='164'>g</text>
<text x='64' y='164'>e</text>
<text x='72' y='164'>s</text>
<text x='824' y='164'>U</text>
<text x='832' y='164'>R</text>
<text x='840' y='164'>F</text>
<text x='848' y='164'>-</text>
<text x='856' y='164'>R</text>
<text x='864' y='164'>a</text>
<text x='872' y='164'>s</text>
<text x='880' y='164'>t</text>
<text x='888' y='164'>e</text>
<text x='896' y='164'>r</text>
<text x='368' y='196'>Q</text>
<text x='376' y='196'>I</text>
<text x='384' y='196'>m</text>
<text x='392' y='196'>a</text>
<text x='400' y='196'>g</text>
<text x='408' y='196'>e</text>
<text x='32' y='228'>S</text>
<text x='40' y='228'>V</text>
<text x='48' y='228'>G</text>
<text x='184' y='228'>Q</text>
<text x='192' y='228'>S</text>
<text x='200' y='228'>v</text>
<text x='208' y='228'>g</text>
<text x='216' y='228'>R</text>
<text x='224' y='228'>e</text>
<text x='232' y='228'>n</text>
<text x='240' y='228'>d</text>
<text x='248' y='228'>e</text>
<text x='256' y='228'>r</text>
<text x='264' y='228'>e</text>
<text x='272' y='228'>r</text>
<text x='824' y='228'>P</text>
<text x='832' y='228'>N</text>
<text x='840' y='228'>G</text>
<text x='848' y='228'>,</text>
<text x='864' y='228'>J</text>
<text x='872' y='228'>P</text>
<text x='880' y='228'>E</text>
<text x='888' y='228'>G</text>
<text x='896' y='228'>,</text>
<text x='824' y='244'>R</text>
<text x='832' y='244'>e</text>
<text x='840' y='244'>v</text>
<text x='848' y='244'>e</text>
<text x='856' y='244'>r</text>
<text x='864' y='244'>s</text>
<text x='872' y='244'>e</text>
<text x='888' y='244'>B</text>
<text x='896' y='244'>M</text>
<text x='904' y='244'>P</text>
<text x='16' y='276'>I</text>
<text x='24' y='276'>f</text>
<text x='40' y='276'>t</text>
<text x='48' y='276'>a</text>
<text x='56' y='276'>r</text>
<text x='64' y='276'>g</text>
<text x='72' y='276'>e</text>
<text x='80' y='276'>t</text>
<text x='96' y='276'>f</text>
<text x='104' y='276'>o</text>
<text x='112' y='276'>r</text>
<text x='120' y='276'>m</text>
<text x='128' y='276'>a</text>
<text x='136' y='276'>t</text>
<text x='152' y='276'>i</text>
<text x='160' y='276'>s</text>
<text x='176' y='276'>t</text>
<text x='184' y='276'>h</text>
<text x='192' y='276'>e</text>
<text x='208' y='276'>s</text>
<text x='216' y='276'>a</text>
<text x='224' y='276'>m</text>
<text x='232' y='276'>e</text>
<text x='240' y='276'>:</text>
<text x='32' y='308'>P</text>
<text x='40' y='308'>o</text>
<text x='48' y='308'>s</text>
<text x='56' y='308'>t</text>
<text x='64' y='308'>s</text>
<text x='72' y='308'>c</text>
<text x='80' y='308'>r</text>
<text x='88' y='308'>i</text>
<text x='96' y='308'>p</text>
<text x='104' y='308'>t</text>
<text x='824' y='308'>P</text>
<text x='832' y='308'>o</text>
<text x='840' y='308'>s</text>
<text x='848' y='308'>t</text>
<text x='856' y='308'>s</text>
<text x='864' y='308'>c</text>
<text x='872' y='308'>r</text>
<text x='880' y='308'>i</text>
<text x='888' y='308'>p</text>
<text x='896' y='308'>t</text>
<text x='32' y='340'>P</text>
<text x='40' y='340'>l</text>
<text x='48' y='340'>a</text>
<text x='56' y='340'>i</text>
<text x='64' y='340'>n</text>
<text x='72' y='340'>t</text>
<text x='80' y='340'>e</text>
<text x='88' y='340'>x</text>
<text x='96' y='340'>t</text>
<text x='184' y='340'>e</text>
<text x='192' y='340'>n</text>
<text x='200' y='340'>s</text>
<text x='208' y='340'>u</text>
<text x='216' y='340'>r</text>
<text x='224' y='340'>e</text>
<text x='240' y='340'>C</text>
<text x='248' y='340'>R</text>
<text x='256' y='340'>+</text>
<text x='264' y='340'>L</text>
<text x='272' y='340'>F</text>
<text x='288' y='340'>n</text>
<text x='296' y='340'>e</text>
<text x='304' y='340'>w</text>
<text x='312' y='340'>l</text>
<text x='320' y='340'>i</text>
<text x='328' y='340'>n</text>
<text x='336' y='340'>e</text>
<text x='344' y='340'>s</text>
<text x='824' y='340'>P</text>
<text x='832' y='340'>l</text>
<text x='840' y='340'>a</text>
<text x='848' y='340'>i</text>
<text x='856' y='340'>n</text>
<text x='864' y='340'>t</text>
<text x='872' y='340'>e</text>
<text x='880' y='340'>x</text>
<text x='888' y='340'>t</text>
<text x='32' y='372'>P</text>
<text x='40' y='372'>N</text>
<text x='48' y='372'>G</text>
<text x='824' y='372'>P</text>
<text x='832' y='372'>N</text>
<text x='840' y='372'>G</text>
<text x='32' y='404'>J</text>
<text x='40' y='404'>P</text>
<text x='48' y='404'>E</text>
<text x='56' y='404'>G</text>
<text x='184' y='404'>b</text>
<text x='192' y='404'>a</text>
<text x='200' y='404'>s</text>
<text x='208' y='404'>e</text>
<text x='216' y='404'>l</text>
<text x='224' y='404'>i</text>
<text x='232' y='404'>n</text>
<text x='240' y='404'>i</text>
<text x='248' y='404'>f</text>
<text x='256' y='404'>y</text>
<text x='824' y='404'>J</text>
<text x='832' y='404'>P</text>
<text x='840' y='404'>E</text>
<text x='848' y='404'>G</text>
<text x='824' y='420'>(</text>
<text x='832' y='420'>B</text>
<text x='840' y='420'>a</text>
<text x='848' y='420'>s</text>
<text x='856' y='420'>e</text>
<text x='864' y='420'>l</text>
<text x='872' y='420'>i</text>
<text x='880' y='420'>n</text>
<text x='888' y='420'>e</text>
<text x='904' y='420'>p</text>
<text x='912' y='420'>r</text>
<text x='920' y='420'>o</text>
<text x='928' y='420'>f</text>
<text x='936' y='420'>i</text>
<text x='944' y='420'>l</text>
<text x='952' y='420'>e</text>
<text x='960' y='420'>)</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -3,9 +3,10 @@ Type=Application
Icon=harbour-seaprint
X-Nemo-Application-Type=silica-qt5
Exec=harbour-seaprint
X-DBusActivatable=true
Name=SeaPrint
#[X-Sailjail]
#Permissions=Internet;Pictures;Documents;Downloads;MediaIndexing;Compatibility
#OrganizationName=net.attah
#ApplicationName=seaprint
[X-Sailjail]
Permissions=Internet;Pictures;Documents;Downloads;RemovableMedia;MediaIndexing;Compatibility
OrganizationName=net.attah
ApplicationName=seaprint
ExecDBus=harbour-seaprint --prestart

View file

@ -26,31 +26,36 @@ INSTALLS += i18n
system(lrelease $$PWD/translations/*.ts)
CONFIG += sailfishapp
QT += svg
PKGCONFIG += mlite5 libcurl poppler glib-2.0 cairo
QT += svg dbus
PKGCONFIG += mlite5 libcurl glib-2.0
INCLUDEPATH += $$system(pkg-config --cflags-only-I poppler-glib | sed 's/-I//g')
LIBS += -lcurl -lglib-2.0 -lgobject-2.0 -ldl
DEFINES += MADNESS=1
DEFINES += PDF_CREATOR='\\"SeaPrint\ $$VERSION\\"'
DEFINES += SEAPRINT_VERSION='\\"$$VERSION\\"'
SOURCES += src/harbour-seaprint.cpp \
src/dbusadaptorbase.cpp \
src/freedesktopdbusadaptor.cpp \
src/rangelistchecker.cpp \
src/convertchecker.cpp \
src/convertworker.cpp \
src/curlrequester.cpp \
src/curlworker.cpp \
src/imageitem.cpp \
src/ippdiscovery.cpp \
src/ippmsg.cpp \
src/ippprinter.cpp \
src/mimer.cpp \
ppm2pwg/printparameters.cpp \
ppm2pwg/ppm2pwg.cpp \
ppm2pwg/pdf2printable.cpp \
ppm2pwg/baselinify.cpp \
ppm2pwg/bytestream/bytestream.cpp \
src/overrider.cpp \
src/settings.cpp \
src/svgprovider.cpp
src/printerworker.cpp \
src/seaprintdbusadaptor.cpp \
src/settings.cpp
DISTFILES += qml/harbour-seaprint.qml \
qml/components/DependentOn.qml \
qml/cover/CoverPage.qml \
qml/components/*qml \
qml/pages/*.qml \
@ -81,27 +86,33 @@ TRANSLATIONS += translations/harbour-seaprint-de.ts \
translations/harbour-seaprint-pl.ts
HEADERS += \
src/dbusadaptorbase.h \
src/freedesktopdbusadaptor.h \
src/rangelistchecker.h \
src/convertchecker.h \
src/convertworker.h \
src/curlrequester.h \
src/curlworker.h \
src/imageitem.h \
src/ippdiscovery.h \
src/ippmsg.h \
src/ippprinter.h \
src/mimer.h \
ppm2pwg/ppm2pwg.h \
ppm2pwg/pdf2printable.h \
ppm2pwg/baselinify.h \
ppm2pwg/madness.h \
ppm2pwg/printparameters.h \
ppm2pwg/PwgPgHdr.h \
ppm2pwg/PwgPgHdr.codable \
ppm2pwg/UrfPgHdr.h \
ppm2pwg/UrfPgHdr.codable \
ppm2pwg/bytestream/bytestream.h \
ppm2pwg/bytestream/codable.h \
ppm2pwg/argget.h \
src/overrider.h \
src/papersizes.h \
src/settings.h \
src/svgprovider.h
src/printerworker.h \
src/seaprintdbusadaptor.h \
src/settings.h
INCLUDEPATH += ppm2pwg \
ppm2pwg/bytestream

@ -1 +1 @@
Subproject commit aa53021c0cdcd6594a8d54955ceccd45e68f6566
Subproject commit acc32f3769c3b18bd6b94c55b882c7b7b778854c

View file

@ -7,33 +7,34 @@ Setting {
property var preferred_choices: []
property string preferred_choice_suffix: ""
property var actual_choices: Utils.fixupChoices(name, choices, parent.selectedFileType)
property var actual_choices: Utils.fixupChoices(name, choices, parent.selectedFileType, parent.printer)
property int num_large_choices: 8
displayValue: Utils.ippName(name, choice != undefined ? choice : default_choice)
displayValue: Utils.ippName(name, choice != undefined ? choice : default_choice, strings)
onClicked: {
if(actual_choices.length>num_large_choices)
if(hasMenu)
{
menu.open(this)
}
else if(actual_choices.length != 0)
{
var dialog = pageStack.push("LargeChoiceDialog.qml",
{name:name, choice: choice != undefined ? choice : default_choice, choices: actual_choices,
preferred_choices: preferred_choices, preferred_choice_suffix: preferred_choice_suffix})
preferred_choices: preferred_choices, preferred_choice_suffix: preferred_choice_suffix,
strings: strings})
dialog.accepted.connect(function() {
choice = dialog.choice
})
}
else
{
menu.open(this)
}
}
menu: ContextMenu {
Repeater {
model: actual_choices.length>num_large_choices ? 0 : actual_choices
MenuItem {
text: Utils.ippName(name, actual_choices[index])
text: Utils.ippName(name, actual_choices[index], strings)
+ (Utils.has(preferred_choices, actual_choices[index]) ? " "+preferred_choice_suffix : "")
onClicked:
{
@ -44,7 +45,5 @@ Setting {
}
hasMenu: !actual_choices.length>num_large_choices
hasMenu: actual_choices.length != 0 && (actual_choices.length <= num_large_choices)
}

View file

@ -71,4 +71,34 @@ GalleryFilterUnion {
property: "fileName"
value: ".RTF"
}
GalleryEndsWithFilter {
property: "fileName"
value: ".ppt"
}
GalleryEndsWithFilter {
property: "fileName"
value: ".pptx"
}
GalleryEndsWithFilter {
property: "fileName"
value: ".odp"
}
GalleryEndsWithFilter {
property: "fileName"
value: ".PPT"
}
GalleryEndsWithFilter {
property: "fileName"
value: ".PPTX"
}
GalleryEndsWithFilter {
property: "fileName"
value: ".ODP"
}
}

View file

@ -11,12 +11,6 @@ Dialog {
property string title
canAccept: valueField.acceptableInput
Component.onCompleted:
{
console.log("adasdsa", title)
}
Column {
width: parent.width
@ -26,10 +20,9 @@ Dialog {
id: valueField
validator: IntValidator{bottom: min; top: max;}
width: parent.width
placeholderText: ""+min+"-"+max
placeholderText: ""+min+"-"+(max == 65535 ? "..." : max)
label: title
focus: true
labelVisible: true
inputMethodHints: Qt.ImhDigitsOnly
}

View file

@ -2,8 +2,21 @@ import QtQuick 2.6
import Sailfish.Silica 1.0
Setting {
property int low: valid ? parent.printer.attrs[name+"-supported"].value.low : 0
property int high: valid ? parent.printer.attrs[name+"-supported"].value.high : 0
property int minimum_high: 0
property int low: _valid ? parent.printer.attrs[name+"-supported"].value.low : (minimum_high != 0) ? 1 : 0
property int high: _valid ? ensure_minimum(parent.printer.attrs[name+"-supported"].value.high) : minimum_high
function ensure_minimum(orig)
{
if(orig < minimum_high)
{
return minimum_high;
}
else
{
return orig;
}
}
property bool suppressChange: false
@ -12,7 +25,6 @@ Setting {
onChoiceChanged: {
if(choice == undefined)
{
console.log("choice unset");
suppressChange = true;
slider.value = slider.minimumValue;
suppressChange = false;
@ -41,7 +53,7 @@ Setting {
anchors.right: parent.right
icon.source: "image://theme/icon-s-edit"
onClicked: {var dialog = pageStack.push(Qt.resolvedUrl("IntegerInputDialog.qml"),
{value: choice, title: prettyName,
{title: prettyName,
min: low, max: high});
dialog.accepted.connect(function() {
choice = dialog.value;

View file

@ -12,6 +12,7 @@ Dialog {
property var choices
property var preferred_choices
property string preferred_choice_suffix
property var strings
canAccept: false
SilicaListView
@ -30,7 +31,7 @@ Dialog {
x: Theme.paddingLarge
anchors.verticalCenter: parent.verticalCenter
highlighted: choices[index]==new_choice
text: Utils.ippName(name, choices[index])
text: Utils.ippName(name, choices[index], strings)
+ (Utils.has(preferred_choices, choices[index]) ? " "+preferred_choice_suffix : "")
}
}

View file

@ -0,0 +1,30 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
import seaprint.rangelistchecker 1.0
Dialog {
id: dialog
allowedOrientations: Orientation.All
property string title
property var value
canAccept: valueField.acceptableInput
Column {
width: parent.width
DialogHeader { }
TextField {
id: valueField
acceptableInput: Object.keys(value).length !== 0
width: parent.width
placeholderText: "1,2,17-42"
label: title
focus: true
labelVisible: true
onTextChanged: { dialog.value = RangeListChecker.parse(text) }
}
}
}

View file

@ -5,6 +5,7 @@ Setting {
property int high
property int choice_low: 1
property int choice_high: 0
property bool acceptRangeList: false
property bool suppressChange: false
@ -41,9 +42,8 @@ Setting {
}
onChoiceChanged: {
if(choice == undefined)
if(choice == undefined || choice.constructor.name !== "Object")
{
console.log("choice unset");
suppressChange = true;
low_slider.value = low_slider.minimumValue;
high_slider.value = high_slider.minimumValue;
@ -51,7 +51,39 @@ Setting {
}
}
displayValue: choice == undefined ? qsTr("all") : ""+choice.low+" - "+choice.high
displayValue: prettify(choice)
function prettify(choice)
{
if(choice == undefined)
{
return qsTr("all");
}
else if(choice.constructor.name === "Object")
{
return (""+choice.low+" - "+choice.high)
}
else
{
var ret = "";
for(var i = 0; i < choice.length; i++)
{
if(i!=0)
{
ret = ret+","
}
if(choice[i].low == choice[i].high)
{
ret=ret+choice[i].low
}
else
{
ret=ret+choice[i].low+"-"+choice[i].high
}
}
return ret
}
}
menu: ContextMenu {
MenuItem {
@ -72,7 +104,7 @@ Setting {
anchors.right: parent.right
icon.source: "image://theme/icon-s-edit"
onClicked: {var dialog = pageStack.push(Qt.resolvedUrl("IntegerInputDialog.qml"),
{value: choice, title: prettyName,
{title: prettyName + " (" + qsTr("Low") + ")",
min: 1, max: high});
dialog.accepted.connect(function() {
choice_low = dialog.value;
@ -100,7 +132,7 @@ Setting {
anchors.right: parent.right
icon.source: "image://theme/icon-s-edit"
onClicked: {var dialog = pageStack.push(Qt.resolvedUrl("IntegerInputDialog.qml"),
{value: choice, title: prettyName,
{title: prettyName + " (" + qsTr("High") + ")",
min: 1, max: high});
dialog.accepted.connect(function() {
choice_high = dialog.value;
@ -110,6 +142,17 @@ Setting {
}
MenuItem {
visible: acceptRangeList
text: qsTr("Advanced")
onClicked: {var dialog = pageStack.push(Qt.resolvedUrl("RangeListInputDialog.qml"),
{title: prettyName});
dialog.accepted.connect(function() {
choice = dialog.value;
})
}
}
}

View file

@ -15,6 +15,8 @@ Item {
property var choice
property var default_choice: parent.getDefaultChoice(name)
property var strings: parent.printer.strings
Component.onCompleted: parent.setInitialChoice(this)
onChoiceChanged: parent.choiceMade(this)

View file

@ -84,6 +84,6 @@ Column {
delete jobParams[setting.subkey];
}
}
console.log(JSON.stringify(jobParams));
// console.log(JSON.stringify(jobParams));
}
}

View file

@ -8,9 +8,10 @@ import "../components"
property var color: "white"
property int level: 100
property int high_level: 100
property int low_level: 0
property int low_level: 10
property string name: ""
property string type: ""
property real value_fraction: level/(high_level == 0 ? 100 : high_level)
spacing: Theme.paddingMedium
bottomPadding: Theme.paddingMedium
@ -19,14 +20,14 @@ import "../components"
{
anchors.verticalCenter: parent.verticalCenter
color: parent.color
value: (level/high_level)
value: value_fraction
}
Label
{
anchors.verticalCenter: parent.verticalCenter
width: Theme.itemSizeExtraSmall
text: ""+(100*level/high_level)+"%"
color: Theme.highlightColor
text: ""+Math.round(100*value_fraction)+"%"
color: level <= low_level ? "red" : Theme.highlightColor
font.pixelSize: Theme.fontSizeExtraSmall
}
Column

View file

@ -20,12 +20,10 @@ Item {
Component.onCompleted: go()
function servicesChanged() {
console.log("services changed");
go();
}
function go() {
console.log("go!")
call("GetServices", undefined,
function(result) {
for (var i = 0; i < result.length; i++) {

View file

@ -13,9 +13,37 @@ ApplicationWindow
{
id: appWin
property Page _mainPage
property bool expectCalligra: true
initialPage: Component { FirstPage { selectedFile: Qt.application.arguments[1] ? Qt.application.arguments[1] : "" } }
function openFile(file) {
selectedFile = file
selectedFileType = Mimer.get_type(selectedFile);
if(selectedFileType == "")
{
selectedFile = ""
notifier.notify(qsTr("Unsupported document format"))
}
if(pageStack.currentPage.busyPage)
{
notifier.notify(qsTr("New file selected"))
}
else
{
pageStack.pop(appWin._mainPage, PageStackAction.Immediate)
}
}
property string selectedFile: ""
property string selectedFileType
initialPage: Component {
FirstPage {
Component.onCompleted: appWin._mainPage = this
}
}
cover: Qt.resolvedUrl("cover/CoverPage.qml")
allowedOrientations: Orientation.All
@ -51,11 +79,9 @@ ApplicationWindow
db_conn.transaction(function (tx) {
var res = tx.executeSql('SELECT * FROM Favourites WHERE ssid=?', [ssid]);
for (var i = 0; i < res.rows.length; i++) {
console.log(res.rows.item(i).url)
favs.push(res.rows.item(i).url);
}
});
console.log(ssid, favs);
return favs
}
@ -77,10 +103,18 @@ ApplicationWindow
}
function simplifyType(mimetype) {
if(Mimer.isImage(mimetype))
if(mimetype == Mimer.SVG)
{
return {simple: "svg", translatable: qsTr("SVGs")};
}
else if(Mimer.isImage(mimetype))
{
return {simple: "image", translatable: qsTr("images")};
}
else if(mimetype == Mimer.Plaintext)
{
return {simple: "plaintext", translatable: qsTr("plaintext")};
}
else
{
return {simple: "document", translatable: qsTr("documents")};
@ -123,7 +157,6 @@ ApplicationWindow
expireTimeout: 4000
function notify(data) {
console.log("notifyMessage", data)
body = data
previewBody = data
publish()
@ -152,5 +185,19 @@ ApplicationWindow
defaultValue: SeaPrintSettings.ignoreSslErrorsDefault
}
ConfigurationValue
{
id: debugLogSetting
key: SeaPrintSettings.debugLogPath
defaultValue: SeaPrintSettings.debugLogDefault
}
ConfigurationValue
{
id: allowExternalConnectionsSetting
key: SeaPrintSettings.allowExternalConnectionsPath
defaultValue: SeaPrintSettings.allowExternalConnectionsDefault
}
}

View file

@ -62,7 +62,7 @@ Page {
AboutLabel {
font.pixelSize: Theme.fontSizeSmall
color: Theme.primaryColor
text: "© 2019-2022 Anton Thomasson et al."
text: "© 2019-2023 Anton Thomasson et al."
}
@ -99,10 +99,10 @@ Page {
font.pixelSize: Theme.fontSizeSmall
text: qsTr("Chinese")+" - dashinfantry\n"+
qsTr("Dutch")+" - JSEHV, Rudi Timmermans\n"+
qsTr("French")+" - ensag-dev, Quentí\n"+
qsTr("German")+" - black-sheep-dev, wheelseal, cnlpete\n"+
qsTr("Polish")+" - atlochowski\n"+
qsTr("Spanish")+" - carmenfdezb"
qsTr("French")+" - ensag-dev, pherjung, phklrz, Quentí\n"+
qsTr("German")+" - black-sheep-dev, cnlpete, wheelseal\n"+
qsTr("Polish")+" - atlochowski, karolkurek\n"+
qsTr("Spanish")+" - carmenfdezb, JSEHV"
}
SectionHeader { text: qsTr("Optional dependencies") }

View file

@ -3,6 +3,8 @@ import Sailfish.Silica 1.0
import "utils.js" as Utils
Page {
property bool busyPage: true
allowedOrientations: Orientation.All
Component.onCompleted: {

View file

@ -12,6 +12,18 @@ Page {
anchors.fill: parent
contentHeight: col.height
PullDownMenu {
visible: identifyLabel.visible
MenuItem {
id: identifyLabel
text: qsTr("Identify printer")
visible: printer.attrs.hasOwnProperty("identify-actions-supported")
onClicked: printer.identify()
}
}
Column {
id: col
@ -69,6 +81,27 @@ Page {
visible: printer.attrs.hasOwnProperty("printer-state-message")
}
SectionHeader {
text: qsTr("Supply status")
visible: printer.attrs.hasOwnProperty("marker-colors")
}
Repeater
{
model: printer.attrs["marker-colors"].value.length
SupplyItem
{
color: printer.attrs["marker-colors"].value[index]
level: printer.attrs["marker-levels"].value[index]
high_level: printer.attrs["marker-high-levels"].value[index]
low_level: printer.attrs["marker-low-levels"].value[index]
name: printer.attrs["marker-names"].value[index]
type: printer.attrs["marker-types"].value[index]
}
}
SectionHeader {
text: qsTr("IPP versions")
visible: printer.attrs.hasOwnProperty("ipp-versions-supported")
@ -115,25 +148,24 @@ Page {
}
SectionHeader {
text: qsTr("Supply status")
visible: printer.attrs.hasOwnProperty("marker-colors")
text: qsTr("Performance")
visible: printer.attrs.hasOwnProperty("pages-per-minute") || printer.attrs.hasOwnProperty("pages-per-minute-color")
}
Repeater
Label
{
model: printer.attrs["marker-colors"].value.length
text: ""+printer.attrs["pages-per-minute"].value+" "+qsTr("pages/min")
visible: printer.attrs.hasOwnProperty("pages-per-minute")
SupplyItem
}
Label
{
color: printer.attrs["marker-colors"].value[index]
level: printer.attrs["marker-levels"].value[index]
high_level: printer.attrs["marker-high-levels"].value[index]
low_level: printer.attrs["marker-low-levels"].value[index]
name: printer.attrs["marker-names"].value[index]
type: printer.attrs["marker-types"].value[index]
text: ""+printer.attrs["pages-per-minute-color"].value+" "+qsTr("pages/min (color)")
visible: printer.attrs.hasOwnProperty("pages-per-minute-color")
}
}
}
}
}

View file

@ -4,6 +4,7 @@ import Sailfish.Pickers 1.0
import seaprint.ippdiscovery 1.0
import seaprint.convertchecker 1.0
import seaprint.ippprinter 1.0
import seaprint.imageitem 1.0
import seaprint.mimer 1.0
import "utils.js" as Utils
@ -11,16 +12,11 @@ Page {
id: page
allowedOrientations: Orientation.All
property string selectedFile: ""
property string selectedFileType
Connections {
target: wifi
onConnectedChanged: {
console.log("conn", wifi.connected, wifi.ssid)
if(wifi.connected) {
var favourites = db.getFavourites(wifi.ssid);
console.log(favourites);
IppDiscovery.favourites = favourites;
}
else {
@ -31,7 +27,6 @@ Page {
property bool initialSSIDchange: true
onSsidChanged: {
console.log("ssid changed", wifi.ssid);
if(!initialSSIDchange)
{
IppDiscovery.reset();
@ -62,12 +57,6 @@ Page {
Component.onCompleted: {
IppDiscovery.discover();
if(selectedFile != "")
{
var type = Mimer.get_type(selectedFile);
console.log(type);
selectedFileType = type;
}
}
property bool nagged: false
@ -75,7 +64,6 @@ Page {
onStatusChanged: {
if(status==PageStatus.Active && !nagged && nagScreenSetting.value != nagScreenSetting.expectedValue)
{
console.log("Can convert from Office:", ConvertChecker.calligra)
if(expectCalligra && !ConvertChecker.calligra)
{
nagged=true
@ -107,7 +95,6 @@ Page {
onClicked: {
var dialog = pageStack.push(Qt.resolvedUrl("AddPrinterDialog.qml"));
dialog.accepted.connect(function() {
console.log("add", wifi.ssid, dialog.value);
db.addFavourite(wifi.ssid, dialog.value);
IppDiscovery.favourites = db.getFavourites(dialog.ssid);
})
@ -148,7 +135,7 @@ Page {
property string name: Utils.unknownForEmptyString(printer.attrs["printer-name"].value)
property var supported_formats: Utils.supported_formats(printer, considerAdditionalFormatsSetting.value)
property bool canPrint: supported_formats.mimetypes.indexOf(selectedFileType) != -1
property bool canPrint: supported_formats.mimetypes.indexOf(appWin.selectedFileType) != -1
Connections {
target: printer
@ -166,7 +153,6 @@ Page {
Connections {
target: page
onRefreshed: {
console.log("onRefreshed")
printer.refresh()
}
}
@ -188,7 +174,7 @@ Page {
{
if(printer.attrs.hasOwnProperty("printer-uuid"))
{
return JSON.parse(db.getJobSettings(printer.attrs["printer-uuid"].value, selectedFileType));
return JSON.parse(db.getJobSettings(printer.attrs["printer-uuid"].value, appWin.selectedFileType));
}
else
{
@ -205,7 +191,7 @@ Page {
return;
}
debugCountReset.restart();
if(selectedFile == "")
if(appWin.selectedFile == "")
{
noFileSelected();
}
@ -215,11 +201,11 @@ Page {
}
else
{
pageStack.push(Qt.resolvedUrl("PrinterPage.qml"), {printer: printer, selectedFile: selectedFile, jobParams: maybeGetParams()})
pageStack.push(Qt.resolvedUrl("PrinterPage.qml"), {printer: printer, selectedFile: appWin.selectedFile, jobParams: maybeGetParams()})
}
}
Image {
ImageItem {
id: icon
anchors.top: parent.top
anchors.left: parent.left
@ -228,13 +214,17 @@ Page {
height: Theme.itemSizeLarge
width: Theme.itemSizeLarge
image: printer.icon
Image {
id: placeholder
anchors.fill: parent
sourceSize.height: height
sourceSize.width: width
source: printer.attrs["printer-icons"] ? "image://ippdiscovery/"+Utils.selectIcon(printer.attrs["printer-icons"].value)
: "image://svg/qml/pages/icon-seaprint-nobg.svg"
// Some printers serve their icons over https with invalid certs...
onStatusChanged: if (status == Image.Error) source = "image://svg/qml/pages/icon-seaprint-nobg.svg"
visible: !parent.valid
source: "icon-seaprint-nobg.svg"
}
}
Image {
@ -252,13 +242,13 @@ Page {
Label {
id: name_label
color: canPrint || selectedFile == "" ? Theme.primaryColor : Theme.secondaryColor
color: canPrint || appWin.selectedFile == "" ? Theme.primaryColor : Theme.secondaryColor
text: name
}
Label {
id: mm_label
color: canPrint || selectedFile == "" ? Theme.primaryColor : Theme.secondaryColor
color: canPrint || appWin.selectedFile == "" ? Theme.primaryColor : Theme.secondaryColor
font.pixelSize: Theme.fontSizeExtraSmall
text: Utils.unknownForEmptyString(printer.attrs["printer-make-and-model"].value)
+ (Utils.existsAndNotEmpty("printer-location", printer) ? " • "+printer.attrs["printer-location"].value : "")
@ -266,7 +256,7 @@ Page {
Label {
id: uri_label
color: canPrint || selectedFile == "" ? Theme.highlightColor : Theme.secondaryColor
color: canPrint || appWin.selectedFile == "" ? Theme.highlightColor : Theme.secondaryColor
font.pixelSize: Theme.fontSizeTiny
text: printer.url
}
@ -287,7 +277,7 @@ Page {
width: Theme.itemSizeExtraSmall/2
visible: supported_formats.pdf
highlightColor: "red"
highlighted: !(selectedFile == "" || canPrint)
highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-pdf"
}
HighlightImage {
@ -295,7 +285,7 @@ Page {
width: Theme.itemSizeExtraSmall/2
visible: supported_formats.postscript
highlightColor: "red"
highlighted: !(selectedFile == "" || canPrint)
highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-other"
}
@ -304,7 +294,7 @@ Page {
width: Theme.itemSizeExtraSmall/2
visible: supported_formats.plaintext
highlightColor: "red"
highlighted: !(selectedFile == "" || canPrint)
highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-document"
}
HighlightImage {
@ -312,15 +302,23 @@ Page {
width: Theme.itemSizeExtraSmall/2
visible: supported_formats.office
highlightColor: "red"
highlighted: !(selectedFile == "" || canPrint)
highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-formatted"
}
HighlightImage {
height: Theme.itemSizeExtraSmall/2
width: Theme.itemSizeExtraSmall/2
visible: supported_formats.office
highlightColor: "red"
highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-presentation"
}
HighlightImage {
height: Theme.itemSizeExtraSmall/2
width: Theme.itemSizeExtraSmall/2
visible: supported_formats.images
highlightColor: "red"
highlighted: !(selectedFile == "" || canPrint)
highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-image"
}
}
@ -354,12 +352,16 @@ Page {
}
}
}
DockedPanel {
Rectangle {
id: fileDock
open: true
height: panelColumn.implicitHeight
width: parent.width
dock: Dock.Bottom
anchors.bottom: parent.bottom
gradient: Gradient {
GradientStop { position: 0.0; color: Theme.rgba(Theme.highlightBackgroundColor, 0.15) }
GradientStop { position: 1.0; color: Theme.rgba(Theme.highlightBackgroundColor, 0.3) }
}
Column {
id: panelColumn
@ -371,7 +373,7 @@ Page {
Row {
id: warningRow
anchors.horizontalCenter: parent.horizontalCenter
visible: Mimer.isOffice(page.selectedFileType)
visible: Mimer.isOffice(appWin.selectedFileType)
HighlightImage {
source: "image://theme/icon-s-warning"
@ -394,7 +396,7 @@ Page {
horizontalAlignment: contentWidth > width ? Text.AlignRight : Text.AlignHCenter
truncationMode: TruncationMode.Fade
text: selectedFile != "" ? selectedFile : qsTr("No file selected")
text: appWin.selectedFile != "" ? appWin.selectedFile : qsTr("No file selected")
SequentialAnimation {
id: noFileSelectedAnimation
@ -407,7 +409,6 @@ Page {
Connections {
target: page
onNoFileSelected: {
console.log("onNoFileSelected")
noFileSelectedAnimation.start()
}
}
@ -448,19 +449,7 @@ Page {
title: qsTr("Choose file")
onSelectedContentPropertiesChanged: {
var mimeType = Mimer.get_type(selectedContentProperties.filePath)
if(mimeType == Mimer.PDF || mimeType == Mimer.Postscript || mimeType == Mimer.Plaintext || Mimer.isOffice(mimeType))
{
page.selectedFile = selectedContentProperties.filePath
page.selectedFileType = mimeType
}
else
{
console.log("UNSUPPORTED", mimeType);
notifier.notify(qsTr("Unsupported document format"))
page.selectedFile = ""
page.selectedFileType = ""
}
appWin.openFile(selectedContentProperties.filePath);
}
}
}
@ -470,8 +459,7 @@ Page {
allowedOrientations: Orientation.All
onSelectedContentPropertiesChanged: {
page.selectedFile = selectedContentProperties.filePath
page.selectedFileType = Mimer.get_type(selectedContentProperties.filePath)
appWin.openFile(selectedContentProperties.filePath);
}
}
}

View file

@ -10,7 +10,6 @@ Page {
Component.onCompleted: {
printer.getJobs()
console.log(JSON.stringify(printer.jobs))
}
Connections {
@ -37,7 +36,6 @@ Page {
MenuItem {
text: qsTr("Refresh")
onClicked: {
console.log(JSON.stringify(printer.jobs))
printer.getJobs();
}
}
@ -64,7 +62,6 @@ Page {
anchors.verticalCenter: parent.verticalCenter
color: Theme.highlightColor
text: printer.jobs[index]["job-id"].value
Component.onCompleted: console.log(JSON.stringify(printer.jobs))
}
Column {
@ -73,7 +70,6 @@ Page {
anchors.verticalCenter: parent.verticalCenter
Label {
text: printer.jobs[index]["job-name"] ? printer.jobs[index]["job-name"].value : qsTr("Untitled job")
Component.onCompleted: console.log(JSON.stringify(printer.jobs))
}
Label {
font.pixelSize: Theme.fontSizeTiny

View file

@ -65,7 +65,7 @@ Page {
width: parent.width
wrapMode: Text.WordWrap
text: qsTr("Install calligra:") + " " + (ConvertChecker.calligra ? qsTr("(done)") : "")
text: qsTr("Install Calligra:") + " " + (ConvertChecker.calligra ? qsTr("(done)") : "")
}
Label {
@ -78,6 +78,16 @@ Page {
Clipper {}
}
Label {
visible: appWin.expectCalligra
width: parent.width
wrapMode: Text.WordWrap
text: qsTr("If you are still seeing this message despite installing Calligra, that can be because of SailJail "+
"in Sailfish OS 4.x before the compatibility permission in 4.4. " +
"You can either upgrade to 4.4 or edit out the SailJail section in /usr/share/applications/harbour-seaprint.desktop")
}
Item{
width: parent.width
height: Theme.itemSizeMedium

View file

@ -61,7 +61,7 @@ Page {
onClicked: {
var tmp = jobParams;
// Varies between documents, would be confusing to save
tmp["page-ranges"] = undefined;
delete tmp["page-ranges"];
db.setJobSettings(printer.attrs["printer-uuid"].value, selectedFileType, JSON.stringify(tmp))
}
}
@ -69,7 +69,6 @@ Page {
MenuItem {
text: qsTr("Print")
onClicked: {
console.log(JSON.stringify(jobParams))
pageStack.replace(Qt.resolvedUrl("BusyPage.qml"),{printer:printer},
PageStackAction.Immediate)
printer.print(jobParams, page.selectedFile)
@ -105,6 +104,8 @@ Page {
tag: IppMsg.Integer
name: "copies"
prettyName: qsTr("Copies")
valid: _valid || Utils.supports_raster(printer)
minimum_high: 99
}
ChoiceSetting {
tag: IppMsg.Keyword
@ -119,6 +120,7 @@ Page {
property var pdfpages: ConvertChecker.pdfPages(selectedFile)
high: pdfpages == 0 ? 65535 : pdfpages
acceptRangeList: true
}
ChoiceSetting {
tag: IppMsg.Integer
@ -149,10 +151,10 @@ Page {
tag: IppMsg.Keyword
name: "print-scaling"
prettyName: qsTr("Scaling")
valid: _valid && selectedFileType == Mimer.JPEG
valid: _valid && Mimer.isImage(selectedFileType) && selectedFileType != Mimer.SVG
DependentOn {
target: transferFormatSetting
values: [Mimer.JPEG]
values: [Mimer.JPEG, Mimer.PNG]
}
}
ChoiceSetting {
@ -197,6 +199,7 @@ Page {
name: "media-top-margin"
subkey: "media-col"
prettyName: qsTr("Top")
valid: _valid && Mimer.isImage(selectedFileType)
}
ChoiceSetting {
visible: marginsButton.active
@ -204,6 +207,7 @@ Page {
name: "media-bottom-margin"
subkey: "media-col"
prettyName: qsTr("Bottom")
valid: _valid && Mimer.isImage(selectedFileType)
}
ChoiceSetting {
visible: marginsButton.active
@ -211,6 +215,7 @@ Page {
name: "media-left-margin"
subkey: "media-col"
prettyName: qsTr("Left")
valid: _valid && Mimer.isImage(selectedFileType)
}
ChoiceSetting {
visible: marginsButton.active
@ -218,6 +223,7 @@ Page {
name: "media-right-margin"
subkey: "media-col"
prettyName: qsTr("Right")
valid: _valid && Mimer.isImage(selectedFileType)
}
}
}

View file

@ -53,6 +53,23 @@ Page {
}
}
TextSwitch {
text: qsTr("Enable debug log")
description: qsTr("Print debug log messages to console (if launched from there) or journal if launched normally. Takes effect on app (re-)start.")
checked: debugLogSetting.value
onCheckedChanged: {
debugLogSetting.value = checked
}
}
TextSwitch {
text: qsTr("Allow external connections")
description: qsTr("Allow SeaPrint to fetch recources like icons and option name dictionaries from addresses that are not the printer itself. Some printer makers store icons on their website as opposed to locally on the printer.")
checked: allowExternalConnectionsSetting.value
onCheckedChanged: {
allowExternalConnectionsSetting.value = checked
}
}
}
}
}

1148
qml/pages/strings.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -4,15 +4,14 @@ Summary: SeaPrint
Version: 0
Release: 1
Group: Qt/Qt
License: LICENSE
URL: http://example.org/
License: GPLv3
URL: https://github.com/attah/harbour-seaprint
Source0: %{name}-%{version}.tar.bz2
Requires: sailfishsilica-qt5 >= 0.10.9
BuildRequires: pkgconfig(sailfishapp) >= 1.0.2
BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5Qml)
BuildRequires: pkgconfig(Qt5Quick)
BuildRequires: pkgconfig(Qt5Svg)
BuildRequires: pkgconfig(mlite5)
BuildRequires: desktop-file-utils
BuildRequires: pkgconfig(libcurl)
@ -20,6 +19,10 @@ BuildRequires: pkgconfig(poppler)
BuildRequires: pkgconfig(poppler-glib)
BuildRequires: pkgconfig(glib-2.0)
BuildRequires: pkgconfig(cairo)
BuildRequires: pkgconfig(libjpeg)
%define _source_payload w6T.xzdio
%define _binary_payload w6T.xzdio
%description
Network printing for Sailfish OS
@ -44,7 +47,7 @@ desktop-file-install --delete-original \
%files
%defattr(-,root,root,-)
%{_bindir}
%{_bindir}/%{name}
%{_datadir}/%{name}
%{_datadir}/applications/%{name}.desktop
%{_datadir}/icons/hicolor/*/apps/%{name}.png

View file

@ -4,7 +4,6 @@
#include "convertchecker.h"
#include <QProcess>
#include <QtDebug>
#include "mimer.h"
ConvertChecker::ConvertChecker() : poppler("libpoppler-glib.so.8")
{

View file

@ -1,584 +0,0 @@
#include "convertworker.h"
#include <sailfishapp.h>
#include "papersizes.h"
#include "convertchecker.h"
#include "mimer.h"
#include <QImage>
#include <QMatrix>
#include <QPainter>
#include <QTextDocument>
#include <QPdfWriter>
#include <QAbstractTextDocumentLayout>
#include "ippprinter.h"
#include "pdf2printable.h"
#include "ppm2pwg.h"
#define OK(call) if(!(call)) throw ConvertFailedException()
ConvertWorker::ConvertWorker(IppPrinter* parent) // : QObject((QObject*)parent) borks multithereading?!
{
_printer = parent;
}
void ConvertWorker::command(Bytestream msg)
{
CurlRequester cr(_printer->httpUrl());
cr.setFinishedCallback(_printer, &IppPrinter::getPrinterAttributesFinished);
qDebug() << "command...";
cr.write((char*)msg.raw(), msg.size());
}
// TODO: de-duplicate
void ConvertWorker::getJobs(Bytestream msg)
{
CurlRequester cr(_printer->httpUrl());
cr.setFinishedCallback(_printer, &IppPrinter::getJobsRequestFinished);
cr.write((char*)msg.raw(), msg.size());
}
void ConvertWorker::cancelJob(Bytestream msg)
{
CurlRequester cr(_printer->httpUrl());
cr.setFinishedCallback(_printer, &IppPrinter::cancelJobFinished);
cr.write((char*)msg.raw(), msg.size());
}
void ConvertWorker::justUpload(QString filename, Bytestream header)
{
try {
emit busyMessage(tr("Printing"));
CurlRequester cr(_printer->httpUrl());
cr.setFinishedCallback(_printer, &IppPrinter::printRequestFinished);
QFile file(filename);
file.open(QFile::ReadOnly);
OK(cr.write((char*)header.raw(), header.size()));
QByteArray tmp = file.readAll();
OK(cr.write(tmp.data(), tmp.length()));
file.close();
}
catch(const ConvertFailedException& e)
{
emit failed(e.what() == QString("") ? tr("Upload error") : e.what());
}
}
void ConvertWorker::convertPdf(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, bool TwoSided, bool Tumble,
quint32 PageRangeLow, quint32 PageRangeHigh, bool BackHFlip, bool BackVFlip)
{
try {
Format format;
emit busyMessage(tr("Printing"));
if(targetFormat == Mimer::URF)
{
format = Format::URF;
}
else if(targetFormat == Mimer::PWG)
{
format = Format::PWG;
}
else if(targetFormat == Mimer::Postscript)
{
format = Format::Postscript;
}
else if (targetFormat == Mimer::PDF)
{
format = Format::PDF;
}
else
{
throw ConvertFailedException(tr("Unsupported target format"));
}
if(Colors == 0)
{
Colors = 3;
}
CurlRequester cr(_printer->httpUrl());
cr.setFinishedCallback(_printer, &IppPrinter::printRequestFinished);
OK(cr.write((char*)header.raw(), header.size()));
write_fun WriteFun([&cr](unsigned char const* buf, unsigned int len) -> bool
{
if(len == 0)
return true;
return cr.write((const char*)buf, len);
});
progress_fun ProgressFun([this](size_t page, size_t total) -> void
{
emit progress(page, total);
});
if(!PaperSizes.contains(PaperSize))
{
qDebug() << "Unsupported paper size" << PaperSize;
throw ConvertFailedException(tr("Unsupported paper size"));
}
QSizeF size = PaperSizes[PaperSize];
float Width = size.width();
float Height = size.height();
int res = pdf_to_printable(filename.toStdString(), WriteFun, Colors, Quality, PaperSize.toStdString(), Width, Height, HwResX, HwResY,
format, TwoSided, Tumble, BackHFlip, BackVFlip, PageRangeLow, PageRangeHigh, ProgressFun);
if(res != 0)
{
throw ConvertFailedException(tr("Conversion failed"));
}
qDebug() << "Finished";
}
catch(const ConvertFailedException& e)
{
emit failed(e.what() == QString("") ? tr("Conversion error") : e.what());
}
}
void ConvertWorker::convertImage(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, QMargins margins)
{
try {
bool urf = false;
bool pdfOrPostscript = false;
QString imageFormat = "";
QStringList supportedImageFormats = {Mimer::JPEG, Mimer::PNG};
if(targetFormat == Mimer::URF)
{
urf = true;
}
else if(targetFormat == Mimer::PWG)
{
//ok
}
else if(targetFormat == Mimer::PDF || targetFormat == Mimer::Postscript)
{
HwResX = HwResY = std::min(HwResX, HwResY);
pdfOrPostscript = true;
}
else if(supportedImageFormats.contains(targetFormat))
{
imageFormat = targetFormat.split("/")[1];
}
else
{
throw ConvertFailedException(tr("Unsupported target format"));
}
if(Colors == 0)
{
Colors = 3;
}
if(urf && (HwResX != HwResY))
{ // URF only supports symmetric resolutions
qDebug() << "Unsupported URF resolution" << PaperSize;
throw ConvertFailedException(tr("Unsupported resolution (dpi)"));
}
if(!PaperSizes.contains(PaperSize))
{
qDebug() << "Unsupported paper size" << PaperSize;
throw ConvertFailedException(tr("Unsupported paper size"));
}
QSizeF size = PaperSizes[PaperSize];
quint32 Width = qRound(size.width()/25.4*HwResX);
quint32 Height = qRound(size.height()/25.4*HwResY);
qDebug() << "Size is" << Width << "x" << Height;
QImage inImage;
if(!inImage.load(filename))
{
qDebug() << "failed to load";
throw ConvertFailedException(tr("Failed to load image"));
}
if(inImage.width() > inImage.height())
{
inImage = inImage.transformed(QMatrix().rotate(270.0));
}
int leftMarginPx = (margins.left()/2540.0)*HwResX;
int rightMarginPx = (margins.right()/2540.0)*HwResX;
int topMarginPx = (margins.top()/2540.0)*HwResY;
int bottomMarginPx = (margins.bottom()/2540.0)*HwResY;
int totalXMarginPx = leftMarginPx+rightMarginPx;
int totalYMarginPx = topMarginPx+bottomMarginPx;
inImage = inImage.scaled(Width-totalXMarginPx, Height-totalYMarginPx,
Qt::KeepAspectRatio, Qt::SmoothTransformation);
if(pdfOrPostscript)
{
QTemporaryFile tmpPdfFile;
tmpPdfFile.open();
QPdfWriter pdfWriter(tmpPdfFile.fileName());
pdfWriter.setCreator("SeaPrint " SEAPRINT_VERSION);
QPageSize pageSize(size, QPageSize::Millimeter);
pdfWriter.setPageSize(pageSize);
pdfWriter.setResolution(HwResX);
QPainter painter(&pdfWriter);
int xOffset = ((pdfWriter.width()-totalXMarginPx)-inImage.width())/2 + leftMarginPx;
int yOffset = ((pdfWriter.height()-totalYMarginPx)-inImage.height())/2 + topMarginPx;
painter.drawImage(xOffset, yOffset, inImage);
painter.end();
convertPdf(tmpPdfFile.fileName(), header, targetFormat, Colors, Quality, PaperSize,
HwResX, HwResY, false, false, 0, 0, false, false);
}
else
{
QImage outImage = QImage(Width, Height, inImage.format());
outImage.fill(Qt::white);
QPainter painter(&outImage);
int xOffset = ((outImage.width()-totalXMarginPx)-inImage.width())/2 + leftMarginPx;
int yOffset = ((outImage.height()-totalYMarginPx)-inImage.height())/2 + topMarginPx;
painter.drawImage(xOffset, yOffset, inImage);
painter.end();
QBuffer buf;
buf.open(QIODevice::ReadWrite);
Bytestream outBts;
if(imageFormat != "")
{ // We are converting to a supported image format
outImage.save(&buf, imageFormat.toStdString().c_str());
buf.seek(0);
outBts = Bytestream(buf.size());
buf.read((char*)(outBts.raw()), buf.size());
}
else
{ // We are converting to a raster format
bool gray = Colors == 0 ? inImage.allGray() : Colors == 1;
outImage.save(&buf, gray ? "pgm" : "ppm");
buf.seek(0);
// Skip header - TODO consider reimplementing
buf.readLine(255);
buf.readLine(255);
buf.readLine(255);
Bytestream inBts(Width*Height*Colors);
if((buf.size()-buf.pos()) != inBts.size())
{
qDebug() << buf.size() << buf.pos() << inBts.size();
throw ConvertFailedException();
}
buf.read((char*)(inBts.raw()), inBts.size());
outBts << (urf ? make_urf_file_hdr(1) : make_pwg_file_hdr());
bmp_to_pwg(inBts, outBts, urf, 1, Colors, Quality, HwResX, HwResY, Width, Height, false, false, PaperSize.toStdString(), false, false);
}
emit busyMessage(tr("Printing"));
CurlRequester cr(_printer->httpUrl());
cr.setFinishedCallback(_printer, &IppPrinter::printRequestFinished);
OK(cr.write((char*)header.raw(), header.size()));
OK(cr.write((char*)(outBts.raw()), outBts.size()));
}
qDebug() << "posted";
}
catch(const ConvertFailedException& e)
{
emit failed(e.what() == QString("") ? tr("Conversion error") : e.what());
}
}
void ConvertWorker::convertOfficeDocument(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, bool TwoSided, bool Tumble,
quint32 PageRangeLow, quint32 PageRangeHigh, bool BackHFlip, bool BackVFlip)
{
try {
if(targetFormat == Mimer::URF && (HwResX != HwResY))
{ // URF only supports symmetric resolutions
qDebug() << "Unsupported URF resolution" << PaperSize;
throw ConvertFailedException(tr("Unsupported resolution (dpi)"));
}
QString ShortPaperSize;
if(CalligraPaperSizes.contains(PaperSize))
{
ShortPaperSize = CalligraPaperSizes[PaperSize];
}
else
{
qDebug() << "Unsupported PDF paper size" << PaperSize;
throw ConvertFailedException(tr("Unsupported PDF paper size"));
}
QProcess CalligraConverter(this);
CalligraConverter.setProgram("calligraconverter");
QStringList CalligraConverterArgs = {"--batch", "--mimetype", Mimer::PDF, "--print-orientation", "Portrait", "--print-papersize", ShortPaperSize};
CalligraConverterArgs << filename;
QTemporaryFile tmpPdfFile;
tmpPdfFile.open();
CalligraConverterArgs << tmpPdfFile.fileName();
qDebug() << "CalligraConverteArgs is" << CalligraConverterArgs;
CalligraConverter.setArguments(CalligraConverterArgs);
CalligraConverter.start();
qDebug() << "CalligraConverter Starting";
if(!CalligraConverter.waitForStarted())
{
qDebug() << "CalligraConverter died";
throw ConvertFailedException();
}
qDebug() << "CalligraConverter Started";
if(!CalligraConverter.waitForFinished(-1))
{
qDebug() << "CalligraConverter failed";
throw ConvertFailedException();
}
// qDebug() << CalligraConverter->readAllStandardError();
quint32 pages = ConvertChecker::instance()->pdfPages(tmpPdfFile.fileName());
if (!pages)
{
qDebug() << "pdfinfo returned 0 pages";
throw ConvertFailedException(tr("Failed to get info about PDF file"));
}
if(PageRangeLow==0)
{
PageRangeLow=1;
}
if(PageRangeHigh==0 || PageRangeHigh > pages)
{
PageRangeHigh=pages;
}
// Actual number of pages to print
pages = PageRangeHigh-PageRangeLow+1;
qDebug() << "PageRangeLow" << PageRangeLow << "PageRangeHigh" << PageRangeHigh << "pages" << pages;
convertPdf(tmpPdfFile.fileName(), header, targetFormat, Colors, Quality, PaperSize, HwResX, HwResY, TwoSided, Tumble,
PageRangeLow, PageRangeHigh, BackHFlip, BackVFlip);
qDebug() << "Finished";
qDebug() << "posted";
}
catch(const ConvertFailedException& e)
{
emit failed(e.what() == QString("") ? tr("Conversion error") : e.what());
}
}
void ConvertWorker::convertPlaintext(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, bool TwoSided, bool Tumble,
bool BackHFlip, bool BackVFlip)
{
try {
if(!PaperSizes.contains(PaperSize))
{
qDebug() << "Unsupported paper size" << PaperSize;
throw ConvertFailedException(tr("Unsupported paper size"));
}
QSizeF size = PaperSizes[PaperSize];
QFile inFile(filename);
if(!inFile.open(QIODevice::ReadOnly))
{
throw ConvertFailedException(tr("Failed to open file"));
}
quint32 resolution = std::min(HwResX, HwResY);
QTemporaryFile tmpPdfFile;
tmpPdfFile.open();
QPdfWriter pdfWriter(tmpPdfFile.fileName());
pdfWriter.setCreator("SeaPrint " SEAPRINT_VERSION);
QPageSize pageSize(size, QPageSize::Millimeter);
pdfWriter.setPageSize(pageSize);
pdfWriter.setResolution(resolution);
qreal docHeight = pageSize.sizePixels(resolution).height();
QTextDocument doc;
QFont font = QFont("Courier");
font.setPointSizeF(1);
qreal charHeight = 0;
// Find the optimal font size
while(true) {
QFont tmpFont = font;
tmpFont.setPointSizeF(font.pointSizeF()+0.5);
QFontMetricsF qfm(tmpFont, &pdfWriter);
charHeight = qfm.lineSpacing();
if(charHeight*66 > docHeight)
{
break;
}
font=tmpFont;
}
QFontMetricsF qfm(font, &pdfWriter);
charHeight = qfm.height();
int textHeight = 60*charHeight;
qreal margin = ((docHeight-textHeight)/2);
qreal mmMargin = margin/(resolution/25.4);
doc.setDefaultFont(font);
(void)doc.documentLayout(); // wat
// Needs to be before painter
pdfWriter.setMargins({mmMargin, mmMargin, mmMargin, mmMargin});
QPainter painter(&pdfWriter);
doc.documentLayout()->setPaintDevice(painter.device());
doc.setDocumentMargin(margin);
// Hack to make the document and pdfWriter margins overlap
// Apparently calls to painter.translate() stack... who knew!
painter.translate(-margin, -margin);
QRectF body = pageSize.rectPixels(resolution);
doc.setPageSize(body.size());
QString allText = inFile.readAll();
if(allText.startsWith("\f"))
{
allText.remove(0, 1);
}
if(allText.endsWith("\f"))
{
allText.chop(1);
}
else if(allText.endsWith("\f\n"))
{
allText.chop(2);
}
QStringList pages = allText.split('\f');
bool first = true;
int pageCount = 0;
foreach(QString page, pages)
{
if(!first)
{
pdfWriter.newPage();
}
first = false;
if(page.endsWith("\n"))
{
page.chop(1);
}
doc.setPlainText(page);
int p = 0; // Page number in this document, starting from 0
while(true)
{
painter.translate(body.left(), body.top() - p*body.height());
QRectF view(0, p*body.height(), body.width(), body.height());
painter.setClipRect(view);
QAbstractTextDocumentLayout::PaintContext context;
context.clip = view;
context.palette.setColor(QPalette::Text, Qt::black);
doc.documentLayout()->draw(&painter, context);
p++;
pageCount++;
if(p >= doc.pageCount())
break;
pdfWriter.newPage();
}
}
painter.end();
convertPdf(tmpPdfFile.fileName(), header, targetFormat, Colors, Quality, PaperSize, HwResX, HwResY,
TwoSided, Tumble, 0, 0, BackHFlip, BackVFlip);
qDebug() << "Finished";
qDebug() << "posted";
}
catch(const ConvertFailedException& e)
{
emit failed(e.what() == QString("") ? tr("Conversion error") : e.what());
}
}
QString ConvertWorker::getPopplerShortPaperSize(QString PaperSize)
{
QString ShortPaperSize;
if(PaperSize == "iso_a4_210x297mm")
{
ShortPaperSize = "A4";
}
else if (PaperSize == "iso_a3_297x420mm")
{
ShortPaperSize = "A3";
}
else if (PaperSize == "na_letter_8.5x11in")
{
ShortPaperSize = "letter";
}
else if (PaperSize == "na_legal_8.5x14in")
{
ShortPaperSize = "legal";
}
else
{
qDebug() << "Unsupported PDF paper size" << PaperSize;
throw ConvertFailedException(tr("Unsupported PDF paper size"));
}
return ShortPaperSize;
}

View file

@ -1,71 +0,0 @@
#ifndef CONVERTWORKER_H
#define CONVERTWORKER_H
#include <QObject>
#include <QtNetwork>
#include "curlrequester.h"
class IppPrinter;
class ConvertFailedException: public std::exception
{
private:
QString _msg;
public:
ConvertFailedException(QString msg = "") : _msg(msg = msg)
{
}
virtual const char* what() const throw()
{
return _msg.toStdString().c_str();
}
};
class ConvertWorker : public QObject
{
Q_OBJECT
public:
ConvertWorker(IppPrinter* parent);
private:
ConvertWorker();
public slots:
void command(Bytestream msg);
void getJobs(Bytestream msg);
void cancelJob(Bytestream msg);
void justUpload(QString filename, Bytestream header);
void convertPdf(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, bool TwoSided, bool Tumble,
quint32 PageRangeLow, quint32 PageRangeHigh, bool BackHFlip, bool BackVFlip);
void convertImage(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, QMargins margins);
void convertOfficeDocument(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, bool TwoSided, bool Tumble,
quint32 PageRangeLow, quint32 PageRangeHigh, bool BackHFlip, bool BackVFlip);
void convertPlaintext(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, bool TwoSided, bool Tumble, bool BackHFlip, bool BackVFlip);
signals:
void done(QNetworkRequest request, QTemporaryFile* data);
void progress(qint64 done, qint64 pages);
void busyMessage(QString msg);
void failed(QString message);
private:
IppPrinter* _printer;
QString getPopplerShortPaperSize(QString PaperSize);
};
#endif // CONVERTWORKER_H

View file

@ -1,28 +1,104 @@
#include "curlrequester.h"
#include <algorithm>
#include "settings.h"
CurlRequester::CurlRequester(QUrl addr) : _addr(addr), _canWrite(1), _canRead(), _reading(false), _done(false), _dest(nullptr), _size(0), _offset(0), _performer(addr, this)
static size_t trampoline(char* dest, size_t size, size_t nmemb, void* userp)
{
_performer.start();
CurlRequester* cid = (CurlRequester*)userp;
return cid->requestWrite(dest, size*nmemb);
}
CurlRequester::CurlRequester(QUrl addr, Role role, Bytestream* singleData)
: _addr(addr), _canWrite(1), _canRead(), _reading(false), _done(false), _dest(nullptr), _size(0), _offset(0), _curl(curl_easy_init())
{
curl_easy_setopt(_curl, CURLOPT_URL, addr.toString().toStdString().c_str());
bool verbose = QLoggingCategory::defaultCategory()->isDebugEnabled();
curl_easy_setopt(_curl, CURLOPT_VERBOSE, verbose);
if(Settings::instance()->ignoreSslErrors())
{
curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYSTATUS, 0L);
}
_opts = NULL;
_opts = curl_slist_append(_opts, "User-Agent: SeaPrint " SEAPRINT_VERSION);
switch (role) {
case IppRequest:
{
curl_easy_setopt(_curl, CURLOPT_POST, 1L);
curl_easy_setopt(_curl, CURLOPT_READFUNCTION, trampoline);
curl_easy_setopt(_curl, CURLOPT_READDATA, this);
_opts = curl_slist_append(_opts, "Expect:");
if(singleData != nullptr)
{
curl_easy_setopt(_curl, CURLOPT_POSTFIELDSIZE, singleData->size());
write((char*)(singleData->raw()), singleData->size());
}
else
{
_opts = curl_slist_append(_opts, "Transfer-Encoding: chunked");
}
_opts = curl_slist_append(_opts, "Content-Type: application/ipp");
_opts = curl_slist_append(_opts, "Accept-Encoding: identity");
break;
}
case HttpGetRequest:
{
curl_easy_setopt(_curl, CURLOPT_HTTPGET, 1L);
break;
}
}
curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, _opts);
_worker = QtConcurrent::run([this](){
Bytestream buf;
curl_easy_setopt(_curl, CURLOPT_WRITEDATA, &buf);
curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, write_callback);
CURLcode res = curl_easy_perform(_curl);
if(res != CURLE_OK)
qDebug() << "curl_easy_perform() failed: " << curl_easy_strerror(res);
_result = res;
_resultMsg = buf;
});
}
CurlRequester::~CurlRequester()
{
while(!_canWrite.tryAcquire(1, 500))
{
if(!_performer.isRunning())
{
break;
}
}
_done = true;
_canRead.release();
_performer.wait();
await();
if(_dest != nullptr)
{
delete _dest;
}
curl_slist_free_all(_opts);
curl_easy_cleanup(_curl);
}
CURLcode CurlRequester::await(Bytestream* data)
{
while(_worker.isRunning() && !_canWrite.tryAcquire(1, 500))
{}
_done = true;
_canRead.release();
_worker.waitForFinished();
if(data != nullptr)
{
(*data) = _resultMsg;
}
return _result;
}
bool CurlRequester::write(const char *data, size_t size)
@ -30,7 +106,7 @@ bool CurlRequester::write(const char *data, size_t size)
qDebug() << "write " << size;
while(!_canWrite.tryAcquire(1, 500))
{
if(!_performer.isRunning())
if(!_worker.isRunning())
{
return false;
}
@ -53,7 +129,7 @@ size_t CurlRequester::requestWrite(char* dest, size_t size)
if(!_reading)
{
_canRead.acquire();
if(_done) // Can only have been set by write() - only relevant to check if strating to write
if(_done) // Can only have been set by await() - only relevant to check if strating to write
{
return 0;
}

View file

@ -4,30 +4,55 @@
#include <QUrl>
#include <QThread>
#include <QSemaphore>
#include <QMetaMethod>
#include <QtConcurrent/QtConcurrent>
#include <curl/curl.h>
#include <bytestream.h>
#include <QDebug>
#include "curlworker.h"
#include <functional>
class CurlRequester : public QObject
{
Q_OBJECT
public:
CurlRequester(QUrl addr);
enum Role {
IppRequest,
HttpGetRequest
};
CurlRequester(QUrl addr, Role role = IppRequest, Bytestream* = nullptr);
~CurlRequester();
template<typename Class, typename Callback>
bool setFinishedCallback(const Class* receiverObject, Callback cb)
{
connect(&_performer, &CurlWorker::done, receiverObject, cb);
return true;
}
CURLcode await(Bytestream* = nullptr);
bool write(const char *data, size_t size);
size_t requestWrite(char* dest, size_t size);
static size_t write_callback(char *ptr, size_t size, size_t nmemb, void* userdata)
{
size_t bytes_to_write = size*nmemb;
((Bytestream*)userdata)->putBytes(ptr, bytes_to_write);
return bytes_to_write;
}
private:
CurlRequester();
// Container for the cURL global init and cleanup
class GlobalEnv
{
public:
GlobalEnv()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
}
~GlobalEnv()
{
curl_global_cleanup();
}
};
// Must be run exactly once, thus static
static GlobalEnv _gEnv;
QUrl _addr;
QSemaphore _canWrite;
@ -35,11 +60,19 @@ private:
bool _reading;
bool _done;
CURLcode _result;
Bytestream _resultMsg;
char* _dest;
size_t _size;
size_t _offset;
CurlWorker _performer;
friend class CurlWorker;
CURL* _curl;
struct curl_slist* _opts = NULL;
QFuture<void> _worker;
};
#endif // CURLREQUESTER_H

View file

@ -1,57 +0,0 @@
#include "curlworker.h"
#include "curlrequester.h"
#include "settings.h"
static size_t trampoline(char* dest, size_t size, size_t nmemb, void* userp)
{
CurlRequester* cid = (CurlRequester*)userp;
return cid->requestWrite(dest, size*nmemb);
}
CurlWorker::CurlWorker(QUrl addr, void* parent)
{
_curl = curl_easy_init();
// if(!curl)
// return false;
curl_easy_setopt(_curl, CURLOPT_URL, addr.toString().toStdString().c_str());
curl_easy_setopt(_curl, CURLOPT_POST, 1L);
curl_easy_setopt(_curl, CURLOPT_READFUNCTION, trampoline);
curl_easy_setopt(_curl, CURLOPT_READDATA, parent);
// curl_easy_setopt(_curl, CURLOPT_VERBOSE, 1L);
if(Settings::instance()->ignoreSslErrors())
{
curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYSTATUS, 0L);
}
_opts = NULL;
_opts = curl_slist_append(_opts, "Expect:");
_opts = curl_slist_append(_opts, "Transfer-Encoding: chunked");
_opts = curl_slist_append(_opts, "Content-Type: application/ipp");
_opts = curl_slist_append(_opts, "User-Agent: SeaPrint " SEAPRINT_VERSION);
_opts = curl_slist_append(_opts, "Accept-Encoding: identity");
curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, _opts);
}
CurlWorker::~CurlWorker()
{
curl_slist_free_all(_opts);
curl_easy_cleanup(_curl);
}
void CurlWorker::run(){
Bytestream buf;
curl_easy_setopt(_curl, CURLOPT_WRITEDATA, &buf);
curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, write_callback);
CURLcode res = curl_easy_perform(_curl);
if(res != CURLE_OK)
qDebug() << "curl_easy_perform() failed: " << curl_easy_strerror(res);
emit done(res, buf);
}

View file

@ -1,51 +0,0 @@
#ifndef CURLWORKER_H
#define CURLWORKER_H
#include <QThread>
#include <QUrl>
#include <curl/curl.h>
#include <QtDebug>
#include <bytestream.h>
class CurlWorker : public QThread
{
Q_OBJECT
public:
CurlWorker(QUrl addr, void* parent);
~CurlWorker();
void run() override;
static size_t write_callback(char *ptr, size_t size, size_t nmemb, void* userdata)
{
size_t bytes_to_write = size*nmemb;
((Bytestream*)userdata)->putBytes(ptr, bytes_to_write);
return bytes_to_write;
}
signals:
void done(CURLcode, Bytestream);
private:
// Container for the cURL global init and cleanup
class GlobalEnv
{
public:
GlobalEnv()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
}
~GlobalEnv()
{
curl_global_cleanup();
}
};
// Must be run exactly once, thus static
static GlobalEnv _gEnv;
CurlWorker();
CURL* _curl = nullptr;
struct curl_slist* _opts;
};
#endif // CURLWORKER_H

25
src/dbusadaptorbase.cpp Normal file
View file

@ -0,0 +1,25 @@
#include "dbusadaptorbase.h"
#include <QtQuick>
void DBusAdaptorBase::doActivate()
{
QMetaObject::invokeMethod(_view->rootObject(), "activate");
}
void DBusAdaptorBase::doOpen(const QString& fileName)
{
QMetaObject::invokeMethod(_view->rootObject(), "openFile", Q_ARG(QVariant, fileName));
doActivate();
}
void DBusAdaptorBase::doCreateFileAndOpen(const QString& fileContents, const QString& name)
{
QTemporaryFile tmpfile(QDir::tempPath() + "/" + name);
tmpfile.setAutoRemove(false);
tmpfile.open();
tmpfile.write(fileContents.toUtf8());
tmpfile.close();
QMetaObject::invokeMethod(_view->rootObject(), "openFile", Q_ARG(QVariant, tmpfile.fileName()));
doActivate();
}

24
src/dbusadaptorbase.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef DBUSADAPTORBASE_H
#define DBUSADAPTORBASE_H
#include <QObject>
#include <QtDBus/QDBusAbstractAdaptor>
#include <QQuickView>
class DBusAdaptorBase
{
public:
DBusAdaptorBase(QQuickView* view)
: _view(view)
{}
void doActivate();
void doOpen(const QString& fileName);
void doCreateFileAndOpen(const QString& fileContents, const QString& name);
protected:
QQuickView* _view;
};
#endif // DBUSADAPTORBASE_H

View file

@ -0,0 +1,31 @@
#include "freedesktopdbusadaptor.h"
#include <QtQuick>
FreedesktopDBusAdaptor::FreedesktopDBusAdaptor(QQuickView *view)
: QDBusAbstractAdaptor(view)
, DBusAdaptorBase(view)
{
}
FreedesktopDBusAdaptor::~FreedesktopDBusAdaptor()
{
}
void FreedesktopDBusAdaptor::Open(const QStringList& uris, const QVariantMap& platformData)
{
qDebug() << platformData;
if (!uris.isEmpty() && uris[0] != "") {
doOpen(uris.at(0));
}
else if(platformData.contains("data"))
{
doCreateFileAndOpen(platformData["data"].toString(),
(platformData.contains("name") ? platformData["name"].toString() : "seaprint"));
}
}
void FreedesktopDBusAdaptor::Activate(const QVariantMap&)
{
QMetaObject::invokeMethod(_view->rootObject(), "activate");
}

View file

@ -0,0 +1,21 @@
#ifndef FREEDESKTOPDBUSADAPTOR_H
#define FREEDESKTOPDBUSADAPTOR_H
#include "dbusadaptorbase.h"
class FreedesktopDBusAdaptor : public QDBusAbstractAdaptor, public DBusAdaptorBase
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Application")
public:
FreedesktopDBusAdaptor(QQuickView *view);
~FreedesktopDBusAdaptor();
public Q_SLOTS:
Q_NOREPLY void Open(const QStringList& uris, const QVariantMap& platformData);
Q_NOREPLY void Activate(const QVariantMap& platformData);
};
#endif // FREEDESKTOPDBUSADAPTOR_H

View file

@ -1,15 +1,23 @@
#include <QtQuick>
#include <QVariant>
#include <QDBusConnection>
#include <sailfishapp.h>
#include <src/ippdiscovery.h>
#include <src/ippprinter.h>
#include <src/imageitem.h>
#include <src/mimer.h>
#include <src/convertchecker.h>
#include <src/svgprovider.h>
#include <src/settings.h>
#include <src/rangelistchecker.h>
#include <src/freedesktopdbusadaptor.h>
#include <src/seaprintdbusadaptor.h>
#include "argget.h"
#include <iostream>
Q_DECLARE_METATYPE(CURLcode)
Q_DECLARE_METATYPE(Bytestream)
Q_DECLARE_METATYPE(PrintParameters)
template <class T>
static QObject* singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
@ -24,8 +32,23 @@ static QObject* singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngi
int main(int argc, char *argv[])
{
bool prestart = false;
std::string FileName;
SwitchArg<bool> pretsartOpt(prestart, {"--prestart"}, "Run prestart");
PosArg fileArg(FileName, "File to print", true);
ArgGet args({&pretsartOpt}, {&fileArg});
if(!args.get_args(argc, argv))
{
return 1;
}
qRegisterMetaType<CURLcode>();
qRegisterMetaType<Bytestream>();
qRegisterMetaType<PrintParameters>();
// Turn on/off logging according to setting
QLoggingCategory::defaultCategory()->setEnabled(QtMsgType::QtDebugMsg, Settings::instance()->debugLog());
QGuiApplication* app = SailfishApp::application(argc, argv);
@ -36,17 +59,36 @@ int main(int argc, char *argv[])
qmlRegisterSingletonType<IppDiscovery>("seaprint.ippdiscovery", 1, 0, "IppDiscovery", singletontype_provider<IppDiscovery>);
qmlRegisterSingletonType<Mimer>("seaprint.mimer", 1, 0, "Mimer", singletontype_provider<Mimer>);
qmlRegisterSingletonType<ConvertChecker>("seaprint.convertchecker", 1, 0, "ConvertChecker", singletontype_provider<ConvertChecker>);
qmlRegisterSingletonType<ConvertChecker>("seaprint.settings", 1, 0, "SeaPrintSettings", singletontype_provider<Settings>);
qmlRegisterSingletonType<Settings>("seaprint.settings", 1, 0, "SeaPrintSettings", singletontype_provider<Settings>);
qmlRegisterSingletonType<RangeListChecker>("seaprint.rangelistchecker", 1, 0, "RangeListChecker", singletontype_provider<RangeListChecker>);
qmlRegisterType<IppPrinter>("seaprint.ippprinter", 1, 0, "IppPrinter");
qmlRegisterType<ImageItem>("seaprint.imageitem", 1, 0, "ImageItem");
qmlRegisterUncreatableType<IppMsg>("seaprint.ippmsg", 1, 0, "IppMsg", "Only used to supply an enum type");
QQuickView* view = SailfishApp::createView();
view->engine()->addImportPath(SailfishApp::pathTo("qml/pages").toString());
view->engine()->addImageProvider(QLatin1String("ippdiscovery"), IppDiscovery::instance());
view->engine()->addImageProvider(QLatin1String("svg"), SvgProvider::instance());
view->setSource(SailfishApp::pathToMainQml());
FreedesktopDBusAdaptor freedesktopDbus(view);
SeaPrintDBusAdaptor seaprintDbus(view);
if (!QDBusConnection::sessionBus().registerObject("/net/attah/seaprint", view))
qWarning() << "Could not register /net/attah/seaprint D-Bus object.";
if (!QDBusConnection::sessionBus().registerService("net.attah.seaprint"))
qWarning() << "Could not register net.attah.seaprint D-Bus service.";
if(!FileName.empty())
{
qDebug() << "Opening" << FileName.c_str();
QVariant fileNameVariant(FileName.c_str());
QMetaObject::invokeMethod(view->rootObject(), "openFile", Q_ARG(QVariant, fileNameVariant));
}
if(!prestart)
{
view->show();
}
return app->exec();
}

29
src/imageitem.cpp Normal file
View file

@ -0,0 +1,29 @@
#include "imageitem.h"
ImageItem::ImageItem()
{
}
void ImageItem::paint(QPainter *painter)
{
QImage scaled = _image.scaledToHeight(boundingRect().height(), Qt::SmoothTransformation);
painter->drawImage(QPoint {0, 0}, scaled);
}
QImage ImageItem::getImage() const
{
return _image;
}
void ImageItem::setImage(const QImage &image)
{
_image = image;
emit imageChanged();
update();
}
bool ImageItem::isValid() const
{
return !_image.isNull();
}

31
src/imageitem.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef IMAGEITEM_H
#define IMAGEITEM_H
#include <QQuickPaintedItem>
#include <QPainter>
#include <QImage>
class ImageItem : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QImage image READ getImage WRITE setImage NOTIFY imageChanged)
Q_PROPERTY(bool valid READ isValid NOTIFY imageChanged)
public:
ImageItem();
void paint(QPainter *painter);
signals:
void imageChanged();
private:
QImage getImage() const;
void setImage(const QImage &image);
bool isValid() const;
QImage _image;
};
#endif // IMAGEITEM_H

View file

@ -37,7 +37,7 @@ QStringList get_addr(Bytestream& bts)
return addr;
}
IppDiscovery::IppDiscovery() : QStringListModel(), QQuickImageProvider(QQuickImageProvider::Image, ForceAsynchronousImageLoading)
IppDiscovery::IppDiscovery() : QStringListModel()
{
socket = new QUdpSocket(this);
connect(socket, &QUdpSocket::readyRead, this, &IppDiscovery::readPendingDatagrams);
@ -93,15 +93,15 @@ void IppDiscovery::sendQuery(quint16 qtype, QStringList addrs) {
QTime now = QTime::currentTime();
QTime aWhileAgo = now.addSecs(-1);
foreach(QString oq, _outstandingQueries.keys())
for(QPair<quint16, QString> oq : _outstandingQueries.keys())
{
if(_outstandingQueries[oq] < aWhileAgo)
{ // Housekeeping for _outstandingQueries
_outstandingQueries.remove(oq);
}
else if(addrs.contains(oq))
else if(oq.first == qtype && addrs.contains(oq.second))
{ // we recently asked about this, remove it
addrs.removeOne(oq);
addrs.removeOne(oq.second);
}
}
@ -121,9 +121,9 @@ void IppDiscovery::sendQuery(quint16 qtype, QStringList addrs) {
query << _transactionid++ << flags << questions << (quint16)0 << (quint16)0 << (quint16)0;
foreach(QString addr, addrs)
for(QString addr : addrs)
{
_outstandingQueries.insert(addr, now);
_outstandingQueries.insert({qtype, addr}, now);
QStringList addrParts = addr.split(".");
QString addrPart, restAddr;
@ -153,9 +153,7 @@ void IppDiscovery::sendQuery(quint16 qtype, QStringList addrs) {
}
QByteArray bytes((char*)(query.raw()), query.size());
socket->writeDatagram(bytes, QHostAddress("224.0.0.251"), 5353);
socket->writeDatagram((char*)(query.raw()), query.size(), QHostAddress("224.0.0.251"), 5353);
}
QString make_addr(QString proto, int defaultPort, quint16 port, QString ip, QString rp)
@ -187,11 +185,15 @@ void IppDiscovery::update()
QList<QPair<QString,QString>> ippsIpRps;
QString target, rp;
foreach(QString it, _ipps)
for(QString it : _ipps)
{
quint16 port = _ports[it];
target = _targets[it];
rp = _rps[it];
if(!_targets.contains(it) || !_ports.contains(it) || !_rps.contains(it))
continue;
quint16 port = _ports.value(it);
target = _targets.value(it);
rp = _rps.value(it);
for(QMultiMap<QString,QString>::Iterator ait = _AAs.begin(); ait != _AAs.end(); ait++)
{
@ -208,11 +210,14 @@ void IppDiscovery::update()
}
}
foreach(QString it, _ipp)
for(QString it : _ipp)
{
quint16 port = _ports[it];
target = _targets[it];
rp = _rps[it];
if(!_targets.contains(it) || !_ports.contains(it) || !_rps.contains(it))
continue;
quint16 port = _ports.value(it);
target = _targets.value(it);
rp = _rps.value(it);
for(QMultiMap<QString,QString>::Iterator ait = _AAs.begin(); ait != _AAs.end(); ait++)
{
@ -234,9 +239,9 @@ void IppDiscovery::update()
// Counting on that _ipp duplicates doesn't resolve fully any erlier than their _ipps counterpart
// TODO?: replace this with some logica that can bpoth add and remove
// TODO?: replace this with some logic that can bpoth add and remove
// and it can consider _favourites, so we can drop cleanUpdate
foreach(QString f, found)
for(QString f : found)
{
if(!this->stringList().contains(f))
{
@ -252,16 +257,23 @@ void IppDiscovery::update()
void IppDiscovery::updateAndQueryPtrs(QStringList& ptrs, QStringList new_ptrs)
{
new_ptrs.removeDuplicates();
foreach(QString ptr, new_ptrs)
for(QString ptr : new_ptrs)
{
if(!ptrs.contains(ptr))
{
ptrs.append(ptr);
}
// If pointer does not resolve to a target or is missing information, query about it
if(!_targets.contains(ptr) || !_ports.contains(ptr) || !_rps.contains(ptr))
{ // if the PTR doesn't already resolve, ask for everything about it
sendQuery(ALL, {ptr});
if(!_rps.contains(ptr))
{
// Avahi *really* hates sending TXT to anything else than a TXT query
qDebug() << "querying txt " << ptr;
sendQuery(TXT, {ptr});
}
if(!_targets.contains(ptr) || !_ports.contains(ptr))
{
qDebug() << "querying srv " << ptr;
sendQuery(SRV, {ptr});
}
}
}
@ -328,6 +340,7 @@ void IppDiscovery::readPendingDatagrams()
{
std::string tmprp;
tmp/tmp.remaining() >> tmprp;
qDebug() << "tmprp" << aaddr << tmprp.c_str();
_rps[aaddr] = tmprp.c_str();
}
}
@ -370,7 +383,7 @@ void IppDiscovery::readPendingDatagrams()
qDebug() << "new ipp ptrs" << new_ipp_ptrs;
qDebug() << "new ipps ptrs" << new_ipps_ptrs;
qDebug() << "ipp ptrs" << _ipp;
qDebug() << "ipp ptrs" << _ipps;
qDebug() << "ipps ptrs" << _ipps;
qDebug() << "rps" << _rps;
qDebug() << "ports" << _ports;
qDebug() << "new targets" << new_targets;
@ -380,12 +393,12 @@ void IppDiscovery::readPendingDatagrams()
// These will send one query per unique new ptr.
// some responders doesn't give TXT records for more than one thing at at time :(
updateAndQueryPtrs(_ipp, new_ipp_ptrs);
updateAndQueryPtrs(_ipps, new_ipps_ptrs);
updateAndQueryPtrs(_ipp, new_ipp_ptrs);
QStringList unresolvedAddrs;
foreach(QString t, new_targets)
for(QString t : new_targets)
{
// If target does not resolve to an address, query about it
if(!_AAs.contains(t))
@ -404,15 +417,8 @@ void IppDiscovery::readPendingDatagrams()
}
QImage IppDiscovery::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{ //TODO: consider caching images (doesn't appear to be needed currently)
Q_UNUSED(requestedSize);
qDebug() << "requesting image" << id;
QImage img;
QNetworkAccessManager* nam = new QNetworkAccessManager();
QUrl url(id);
bool IppDiscovery::resolve(QUrl& url)
{
QString host = url.host();
if(host.endsWith("."))
@ -420,31 +426,11 @@ QImage IppDiscovery::requestImage(const QString &id, QSize *size, const QSize &r
host.chop(1);
}
qDebug() << url.host() << host << _AAs;
// TODO IPv6
if(_AAs.contains(host))
{ // TODO: retry potential other IPs
url.setHost(_AAs.value(host));
return true;
}
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::UserAgentHeader, "SeaPrint " SEAPRINT_VERSION);
connect(nam, &QNetworkAccessManager::sslErrors, &IppPrinter::ignoreSslErrors);
QNetworkReply* reply = nam->get(request);
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
if (reply->error() == QNetworkReply::NoError)
{
QImageReader imageReader(reply);
img = imageReader.read();
}
*size = img.size();
delete nam;
return img;
return false;
}

View file

@ -1,16 +1,11 @@
#ifndef IPPDISCOVERY_H
#define IPPDISCOVERY_H
#include <QStringListModel>
#include <QQuickImageProvider>
#include <QUdpSocket>
#include <QMutex>
#include <QImageReader>
#include <QtNetwork>
#include <QNetworkAccessManager>
#include <QEventLoop>
#include "bytestream.h"
class IppDiscovery : public QStringListModel, public QQuickImageProvider
class IppDiscovery : public QStringListModel
{
Q_OBJECT
public:
@ -21,6 +16,8 @@ public:
Q_INVOKABLE void discover();
Q_INVOKABLE void reset();
bool resolve(QUrl& url);
signals:
void favouritesChanged();
void ignoreSslErrorsChanged();
@ -47,9 +44,6 @@ private:
void updateAndQueryPtrs(QStringList& ptrs, QStringList new_ptrs);
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
QStringList _ipp;
QStringList _ipps;
QMap<QString,QString> _rps;
@ -59,7 +53,7 @@ private:
QMultiMap<QString,QString> _AAs;
QMultiMap<QString,QString> _AAAAs;
QMap<QString, QTime> _outstandingQueries;
QMap<QPair<quint16, QString>, QTime> _outstandingQueries;
QStringList _favourites;
QUdpSocket* socket;

View file

@ -6,8 +6,9 @@ IppMsg::IppMsg()
{
}
IppMsg::IppMsg(QJsonObject opAttrs, QJsonObject jobAttrs, quint8 majVsn, quint8 minVsn)
IppMsg::IppMsg(Operation operation, QJsonObject opAttrs, QJsonObject jobAttrs, quint8 majVsn, quint8 minVsn)
{
_operation = operation;
_majVsn = majVsn;
_minVsn = minVsn;
_opAttrs = opAttrs;
@ -76,40 +77,63 @@ QJsonValue IppMsg::consume_value(quint8 tag, Bytestream& data)
Q_ASSERT(false);
case Integer:
case Enum:
quint32 tmp_u32;
data >> tmp_len >> tmp_u32;
{
quint32 tmp_u32=0;
if(!(data >>= (quint16)0))
{
data >> (quint16)4 >> tmp_u32;
}
value = (int)tmp_u32;
break;
}
case Boolean:
quint8 tmp_bool;
data >> tmp_len >> tmp_bool;
{
quint8 tmp_bool=0;
if(!(data >>= (quint16)0))
{
data >> (quint16)1 >> tmp_bool;
}
value = (bool)tmp_bool;
break;
}
case DateTime:
{
QDateTime tmp_datetime;
if(!(data >>= (quint16)0))
{
quint16 year;
quint8 month, day, hour, minutes, seconds, deci_seconds,
plus_minus, utc_h_offset, utc_m_offset;
data >> tmp_len >> year >> month >> day >> hour >> minutes >> seconds >> deci_seconds
data >> (quint16)11 >> year >> month >> day >> hour >> minutes >> seconds >> deci_seconds
>> plus_minus >> utc_h_offset >> utc_m_offset;
QDate date(year, month, day);
QTime time(hour, minutes, seconds, deci_seconds*100);
int offset_seconds = (plus_minus == '+' ? 1 : -1)*(utc_h_offset*60*60+utc_m_offset*60);
value = QDateTime(date, time, Qt::OffsetFromUTC, offset_seconds).toString(Qt::ISODate);
tmp_datetime = QDateTime(date, time, Qt::OffsetFromUTC, offset_seconds);
}
value = tmp_datetime.toString(Qt::ISODate);
break;
}
case Resolution:
{
qint32 x, y;
qint8 units;
data >> tmp_len >> x >> y >> units;
qint32 x=0;
qint32 y=0;
qint8 units=0;
if(!(data >>= (quint16)0))
{
data >> (quint16)9 >> x >> y >> units;
}
value = QJsonObject {{"x", x}, {"y", y}, {"units", units}};
break;
}
case IntegerRange:
{
qint32 low, high;
data >> tmp_len >> low >> high;
qint32 low=0;
qint32 high=0;
if(!(data >>= (quint16)0))
{
data >> (quint16)8 >> low >> high;
}
value = QJsonObject {{"low", low}, {"high", high}};
break;
}
@ -159,7 +183,7 @@ QJsonArray IppMsg::get_unnamed_attributes(Bytestream& data)
QJsonArray untag_values(QJsonArray taggedValues)
{
QJsonArray res;
foreach(QJsonValue it, taggedValues)
for(QJsonValue it : taggedValues)
{
res.append(it.toObject()["value"]);
}
@ -251,17 +275,11 @@ QString IppMsg::consume_attribute(QJsonObject& attrs, Bytestream& data)
name = tmp_str.c_str();
taggedValue = consume_value(tag, data);
QJsonArray unnamed = get_unnamed_attributes(data);
// qDebug() << name0 << tag << tmp_len << value << unnamed;
if(tag == BeginCollection)
{
// qDebug() << "Unnamed attrs for collection" << unnamed;
QJsonValue collected = collect_attributes(unnamed);
// qDebug() << "collected" << collected;
taggedValue = QJsonObject {{"tag", tag}, {"value", collected}};
}
@ -270,6 +288,7 @@ QString IppMsg::consume_attribute(QJsonObject& attrs, Bytestream& data)
|| name.startsWith("printer-firmware")
|| name.endsWith("-supported")
|| name == "printer-icons"
|| name == "media-ready"
|| name.endsWith("-reasons")));
if(!unnamed.empty() || forceArray)
@ -285,13 +304,13 @@ QString IppMsg::consume_attribute(QJsonObject& attrs, Bytestream& data)
return name;
}
Bytestream IppMsg::encode(Operation op)
Bytestream IppMsg::encode()
{
Bytestream ipp;
ipp << _majVsn << _minVsn;
ipp << quint16(op);
ipp << quint16(_operation);
ipp << _reqid++;
@ -301,12 +320,16 @@ Bytestream IppMsg::encode(Operation op)
QStringList InitialAttrs = {"attributes-charset",
"attributes-natural-language",
"printer-uri",
"job-id",
"requesting-user-name"};
foreach(QString key, InitialAttrs)
for(QString key : InitialAttrs)
{
if(_opAttrs.find(key) != _opAttrs.end())
{
QJsonObject val = _opAttrs.take(key).toObject();
encode_attr(ipp, val["tag"].toInt(), key, val["value"]);
}
}
for(QJsonObject::iterator it = _opAttrs.begin(); it != _opAttrs.end(); it++)
{ // encode any remaining op-attrs
QJsonObject val = it.value().toObject();
@ -339,7 +362,25 @@ void IppMsg::encode_attr(Bytestream& msg, quint8 tag, QString name, QJsonValue v
name = "";
}
msg << tag << quint16(name.length()) << name.toStdString();
if(value.isArray())
{
QJsonArray array = value.toArray();
value = array.takeAt(0);
encode_value(msg, tag, value);
for(QJsonValue v : array)
{
msg << tag << quint16(0);
encode_value(msg, tag, v);
}
}
else
{
encode_value(msg, tag, value);
}
}
void IppMsg::encode_value(Bytestream& msg, quint8 tag, QJsonValue value)
{
switch (tag) {
case OpAttrs:
@ -356,7 +397,7 @@ void IppMsg::encode_attr(Bytestream& msg, quint8 tag, QString name, QJsonValue v
}
case Boolean:
{
quint32 tmp_u8 = value.toBool();
quint8 tmp_u8 = value.toBool();
msg << (quint16)1 << tmp_u8;
break;
}
@ -383,20 +424,12 @@ void IppMsg::encode_attr(Bytestream& msg, quint8 tag, QString name, QJsonValue v
case BeginCollection:
{
msg << (quint16)0; // length of value
if(value.isObject())
{
QJsonObject collection = value.toObject();
foreach(QString key, collection.keys())
for(QString key : collection.keys())
{
encode_attr(msg, collection[key].toObject()["tag"].toInt(), key,
collection[key].toObject()["value"], true);
}
}
else
{
// TODO add support for 1-setOf in collections
Q_ASSERT("FIXME-array");
}
msg << (quint8)EndCollection << (quint16)0 << (quint16)0;
break;
}
@ -423,3 +456,8 @@ void IppMsg::encode_attr(Bytestream& msg, quint8 tag, QString name, QJsonValue v
break;
}
}
void IppMsg::setOpAttr(QString name, IppMsg::IppTag type, QJsonValue value)
{
_opAttrs.insert(name, QJsonObject {{"tag", type}, {"value", value}});
}

View file

@ -4,11 +4,11 @@
#include "bytestream.h"
#include <QObject>
#include <QJsonObject>
#include <QJsonArray>
#include <QDate>
#include <QTime>
#include <QDebug>
#include <QUrl>
#include <QtNetwork>
#include <QNetworkReply>
#include <QIODevice>
class IppMsg
{
@ -61,20 +61,23 @@ public:
RestartJob = 0x000E,
PausePrinter = 0x0010,
ResumePrinter = 0x0011,
PurgeJobs = 0x0012
PurgeJobs = 0x0012,
IdentifyPrinter = 0x003C
};
explicit IppMsg();
explicit IppMsg(Bytestream& resp);
IppMsg(QJsonObject opAttrs, QJsonObject jobAttrs=QJsonObject(), quint8 majVsn=1, quint8 minVsn=1);
IppMsg(Operation operation, QJsonObject opAttrs, QJsonObject jobAttrs=QJsonObject(), quint8 majVsn=1, quint8 minVsn=1);
IppMsg(const IppMsg& other) = default;
~IppMsg();
Bytestream encode(Operation op);
Bytestream encode();
QJsonObject getPrinterAttrs() {return _printerAttrs;}
QJsonArray getJobAttrs() {return _jobAttrs;}
QJsonObject getOpAttrs() {return _opAttrs;}
void setOpAttr(QString name, IppMsg::IppTag type, QJsonValue value);
quint16 getStatus() {return _status;}
@ -85,6 +88,9 @@ private:
QJsonValue collect_attributes(QJsonArray& attrs);
QString consume_attribute(QJsonObject& attrs, Bytestream& data);
void encode_attr(Bytestream& msg, quint8 tag, QString name, QJsonValue value, bool subCollection=false);
void encode_value(Bytestream& msg, quint8 tag, QJsonValue value);
Operation _operation;
quint8 _majVsn;
quint8 _minVsn;

View file

@ -1,42 +1,41 @@
#include "ippprinter.h"
#include "ippdiscovery.h"
#include "mimer.h"
#include "convertchecker.h"
#include "papersizes.h"
#include "overrider.h"
#include "settings.h"
#include <fstream>
Q_DECLARE_METATYPE(QMargins)
Q_DECLARE_METATYPE(IppMsg)
IppPrinter::IppPrinter()
{
_worker = new PrinterWorker(this);
QObject::connect(this, &IppPrinter::urlChanged, this, &IppPrinter::onUrlChanged);
qRegisterMetaType<QTemporaryFile*>("QTemporaryFile*");
_worker = new ConvertWorker(this);
_worker->moveToThread(&_workerThread);
connect(this, &IppPrinter::doDoGetPrinterAttributes, _worker, &PrinterWorker::getPrinterAttributes);
connect(this, &IppPrinter::doGetJobs, _worker, &PrinterWorker::getJobs);
connect(this, &IppPrinter::doCancelJob, _worker, &PrinterWorker::cancelJob);
connect(this, &IppPrinter::doIdentify, _worker, &PrinterWorker::identify);
connect(this, &IppPrinter::doPrint, _worker, &PrinterWorker::print);
connect(this, &IppPrinter::doPrint2, _worker, &PrinterWorker::print2);
connect(&_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
connect(this, &IppPrinter::doGetStrings, _worker, &PrinterWorker::getStrings);
connect(this, &IppPrinter::doGetImage, _worker, &PrinterWorker::getImage);
connect(this, &IppPrinter::doCommand, _worker, &ConvertWorker::command);
connect(this, &IppPrinter::doGetJobs, _worker, &ConvertWorker::getJobs);
connect(this, &IppPrinter::doCancelJob, _worker, &ConvertWorker::cancelJob);
connect(this, &IppPrinter::doJustUpload, _worker, &ConvertWorker::justUpload);
connect(this, &IppPrinter::doConvertPdf, _worker, &ConvertWorker::convertPdf);
connect(this, &IppPrinter::doConvertImage, _worker, &ConvertWorker::convertImage);
connect(this, &IppPrinter::doConvertOfficeDocument, _worker, &ConvertWorker::convertOfficeDocument);
connect(this, &IppPrinter::doConvertPlaintext, _worker, &ConvertWorker::convertPlaintext);
connect(_worker, &ConvertWorker::progress, this, &IppPrinter::setProgress);
connect(_worker, &ConvertWorker::busyMessage, this, &IppPrinter::setBusyMessage);
connect(_worker, &ConvertWorker::failed, this, &IppPrinter::convertFailed);
connect(_worker, &PrinterWorker::progress, this, &IppPrinter::setProgress);
connect(_worker, &PrinterWorker::busyMessage, this, &IppPrinter::setBusyMessage);
connect(_worker, &PrinterWorker::failed, this, &IppPrinter::convertFailed);
qRegisterMetaType<QMargins>();
_workerThread.start();
qRegisterMetaType<IppMsg>();
}
IppPrinter::~IppPrinter() {
// TODO: delete worker and workerthread?
}
QJsonObject IppPrinter::opAttrs() {
@ -55,8 +54,6 @@ void IppPrinter::setUrl(QString url_s)
{
QUrl url = QUrl(url_s);
qDebug() << url.scheme();
// If not already a good scheme, try to fixup, or give an empty url
if(url.scheme() != "ipp" && url.scheme() != "ipps" && url.scheme() != "file")
{
@ -86,44 +83,110 @@ void IppPrinter::setUrl(QString url_s)
void IppPrinter::onUrlChanged()
{
QMetaObject::invokeMethod(_worker, "urlChanged", Q_ARG(QUrl, httpUrl()));
refresh();
}
void IppPrinter::refresh() {
// _attrs = QJsonObject();
// emit attrsChanged();
// _additionalDocumentFormats = QStringList();
// emit additionalDocumentFormatsChanged();
if(_url.scheme() == "file")
{
_attrs = QJsonObject();
QString localFile = _url.toLocalFile();
QFile file(_url.toLocalFile());
if(localFile.endsWith(".raw") || localFile.endsWith(".bin"))
{
try
{
std::ifstream ifs = std::ifstream(localFile.toStdString(), std::ios::in | std::ios::binary);
Bytestream InBts(ifs);
IppMsg resp(InBts);
qDebug() << resp.getStatus() << resp.getOpAttrs() << resp.getPrinterAttrs();
_attrs = resp.getPrinterAttrs();
}
catch(const std::exception& e)
{
qDebug() << e.what();
}
}
else
{
QFile file(localFile);
if(file.open(QIODevice::ReadOnly))
{
QJsonDocument JsonDocument = QJsonDocument::fromJson(file.readAll());
_attrs = JsonDocument.object();
// These won't load anyway...r
_attrs.remove("printer-icons");
file.close();
Overrider::instance()->apply(_attrs);
}
}
Overrider::instance()->apply(_attrs);
emit attrsChanged();
// For testing fake file-prinetrs with a strings file hosted elsewhere
// MaybeGetIcon();
// MaybeGetStrings();
UpdateAdditionalDocumentFormats();
}
else
{
QJsonObject o = opAttrs();
IppMsg msg = IppMsg(o);
emit doCommand(msg.encode(IppMsg::GetPrinterAttrs));
IppMsg msg = IppMsg(IppMsg::GetPrinterAttrs, o);
emit doDoGetPrinterAttributes(msg.encode());
}
}
void IppPrinter::MaybeGetStrings()
{
if(_attrs.contains("printer-strings-uri") && _strings.empty())
{
QUrl url(_attrs["printer-strings-uri"].toObject()["value"].toString());
resolveUrl(url);
if(isAllowedAddress(url))
{
emit doGetStrings(url);
}
}
}
void IppPrinter::MaybeGetIcon(bool retry)
{
if(_attrs.contains("printer-icons") && (_icon.isNull() || retry) && !_iconRetried)
{
QUrl url;
QJsonArray icons = _attrs["printer-icons"].toObject()["value"].toArray();
if(retry)
{ // If there were more than one icon, try the last one on the retry
if(icons.size() > 1)
{
url = icons.last().toString();
}
}
else
{
if(icons.size() == 3)
{ // If there are 3 icons, the first will be the 48px one, ignore it
url = icons.at(1).toString();
}
else
{
url = icons.at(0).toString();
}
}
resolveUrl(url);
if(isAllowedAddress(url))
{
emit doGetImage(url);
}
}
_iconRetried = retry;
}
void IppPrinter::UpdateAdditionalDocumentFormats()
{
_additionalDocumentFormats = QStringList();
@ -175,6 +238,8 @@ void IppPrinter::getPrinterAttributesFinished(CURLcode res, Bytestream data)
emit attrsChanged();
MaybeGetStrings();
MaybeGetIcon();
UpdateAdditionalDocumentFormats();
}
@ -247,14 +312,48 @@ void IppPrinter::cancelJobFinished(CURLcode res, Bytestream data)
getJobs();
}
void IppPrinter::ignoreSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
void IppPrinter::identifyFinished(CURLcode /*res*/, Bytestream /*data*/)
{
bool ignore = Settings::instance()->ignoreSslErrors();
qDebug() << reply->request().url() << "SSL handshake failed" << errors << ignore;
if(ignore)
}
void IppPrinter::getStringsFinished(CURLcode res, Bytestream data)
{
qDebug() << res << data.size();
if(res == CURLE_OK)
{
reply->ignoreSslErrors(errors);
QByteArray ba((char*)data.raw(), data.size());
// "media-type.com.epson-coated" = "Epson Photo Quality Ink Jet";
QRegularExpression re("^\\\"(.*)\\\"\\s*=\\s*\\\"(.*)\\\";");
QList<QByteArray> bl = ba.split('\n');
for(QByteArray l : bl)
{
QRegularExpressionMatch match = re.match(l);
if(match.hasMatch())
{
_strings[match.captured(1)] = match.captured(2);
}
}
}
}
void IppPrinter::getImageFinished(CURLcode res, Bytestream data)
{
qDebug() << res << data.size();
if(res == CURLE_OK)
{
QImage tmp;
if(tmp.loadFromData(data.raw(), data.size(), "PNG"))
{
_icon = tmp;
qDebug() << "image loaded" << _icon;
emit iconChanged();
if(tmp.size().width() < 128)
{
MaybeGetIcon(true);
}
}
}
}
@ -282,17 +381,27 @@ QString targetFormatIfAuto(QString documentFormat, QString mimeType, QJsonArray
{
if(documentFormat == Mimer::OctetStream)
{
if(mimeType == Mimer::PDF || mimeType == Mimer::Plaintext)
QStringList PdfPrioList = {Mimer::PDF, Mimer::Postscript, Mimer::PWG, Mimer::URF};
if(mimeType == Mimer::PDF)
{
return firstMatch(supportedMimeTypes, {Mimer::PDF, Mimer::Postscript, Mimer::PWG, Mimer::URF });
return firstMatch(supportedMimeTypes, PdfPrioList);
}
else if(mimeType == Mimer::Postscript)
{
return firstMatch(supportedMimeTypes, {Mimer::Postscript});
}
else if(mimeType == Mimer::Plaintext)
{
return firstMatch(supportedMimeTypes, PdfPrioList << Mimer::Plaintext);
}
else if(Mimer::isOffice(mimeType))
{
return firstMatch(supportedMimeTypes, {Mimer::PDF, Mimer::Postscript, Mimer::PWG, Mimer::URF });
return firstMatch(supportedMimeTypes, PdfPrioList);
}
else if(documentFormat == Mimer::SVG)
{
QStringList SvgPrioList {Mimer::PWG, Mimer::URF, Mimer::PDF, Mimer::Postscript};
return firstMatch(supportedMimeTypes, SvgPrioList);
}
else if(Mimer::isImage(mimeType))
{
@ -310,29 +419,28 @@ QString targetFormatIfAuto(QString documentFormat, QString mimeType, QJsonArray
return documentFormat;
}
void IppPrinter::adjustRasterSettings(QString documentFormat, QJsonObject& jobAttrs, quint32& HwResX, quint32& HwResY,
bool& BackHFlip, bool& BackVFlip)
void IppPrinter::adjustRasterSettings(QString filename, QString mimeType, QJsonObject& jobAttrs, PrintParameters& Params)
{
if(documentFormat != Mimer::PWG && documentFormat != Mimer::URF)
if(Params.format != PrintParameters::PWG && Params.format != PrintParameters::URF)
{
return;
}
//TODO? jobAttrs.remove("printer-resolution");
jobAttrs.remove("printer-resolution");
if(documentFormat == Mimer::PWG)
if(Params.format == PrintParameters::PWG)
{
quint32 diff = std::numeric_limits<quint32>::max();
quint32 AdjustedHwResX = HwResX;
quint32 AdjustedHwResY = HwResY;
foreach(QJsonValue res, _attrs["pwg-raster-document-resolution-supported"].toObject()["value"].toArray())
quint32 AdjustedHwResX = Params.hwResW;
quint32 AdjustedHwResY = Params.hwResH;
for(QJsonValue res : _attrs["pwg-raster-document-resolution-supported"].toObject()["value"].toArray())
{
QJsonObject resObj = res.toObject();
if(resObj["units"] != 3)
{
continue;
}
quint32 tmpDiff = std::abs(int(HwResX-resObj["x"].toInt())) + std::abs(int(HwResY-resObj["y"].toInt()));
quint32 tmpDiff = std::abs(int(Params.hwResW-resObj["x"].toInt())) + std::abs(int(Params.hwResH-resObj["y"].toInt()));
if(tmpDiff < diff)
{
diff = tmpDiff;
@ -340,27 +448,26 @@ void IppPrinter::adjustRasterSettings(QString documentFormat, QJsonObject& jobAt
AdjustedHwResY = resObj["y"].toInt();
}
}
HwResX = AdjustedHwResX;
HwResY = AdjustedHwResY;
Params.hwResW = AdjustedHwResX;
Params.hwResH = AdjustedHwResY;
}
if(documentFormat == Mimer::URF)
{ // Ensure symmetric resolution for URF
HwResX = HwResY = std::min(HwResX, HwResY);
else if(Params.format == PrintParameters::URF)
{ // Ensure Params.format resolution for URF
Params.hwResW = Params.hwResH = std::min(Params.hwResW, Params.hwResH);
quint32 diff = std::numeric_limits<quint32>::max();
quint32 AdjustedHwRes = HwResX;
quint32 AdjustedHwRes = Params.hwResW;
QJsonArray urfSupported = _attrs["urf-supported"].toObject()["value"].toArray();
foreach(QJsonValue us, urfSupported)
for(QJsonValue us : urfSupported)
{
if(us.toString().startsWith("RS"))
{ //RS300[-600]
QStringList resolutions = us.toString().mid(2).split("-");
foreach(QString res, resolutions)
for(QString res : resolutions)
{
int intRes = res.toInt();
quint32 tmpDiff = std::abs(int(HwResX - intRes));
quint32 tmpDiff = std::abs(int(Params.hwResW - intRes));
if(tmpDiff < diff)
{
diff = tmpDiff;
@ -368,7 +475,7 @@ void IppPrinter::adjustRasterSettings(QString documentFormat, QJsonObject& jobAt
}
}
HwResX = HwResY = AdjustedHwRes;
Params.hwResW = Params.hwResH = AdjustedHwRes;
break;
}
}
@ -378,64 +485,105 @@ void IppPrinter::adjustRasterSettings(QString documentFormat, QJsonObject& jobAt
if(Sides != "" && Sides != "one-sided")
{
if(documentFormat == Mimer::PWG)
if(Params.format == PrintParameters::PWG)
{
QString DocumentSheetBack = _attrs["pwg-raster-document-sheet-back"].toObject()["value"].toString();
if(Sides=="two-sided-long-edge")
{
if(DocumentSheetBack=="flipped")
{
BackVFlip=true;
Params.backVFlip=true;
}
else if(DocumentSheetBack=="rotated")
{
BackHFlip=true;
BackVFlip=true;
Params.backHFlip=true;
Params.backVFlip=true;
}
}
else if(Sides=="two-sided-short-edge")
{
if(DocumentSheetBack=="flipped")
{
BackHFlip=true;
Params.backHFlip=true;
}
else if(DocumentSheetBack=="manual-tumble")
{
BackHFlip=true;
BackVFlip=true;
Params.backHFlip=true;
Params.backVFlip=true;
}
}
}
else if(documentFormat == Mimer::URF)
else if(Params.format == PrintParameters::URF)
{
QJsonArray URfSupported = _attrs["urf-supported"].toObject()["value"].toArray();
if(Sides=="two-sided-long-edge")
{
if(URfSupported.contains("DM2"))
{
BackVFlip=true;
Params.backVFlip=true;
}
else if(URfSupported.contains("DM3"))
{
BackHFlip=true;
BackVFlip=true;
Params.backHFlip=true;
Params.backVFlip=true;
}
}
else if(Sides=="two-sided-short-edge")
{
if(URfSupported.contains("DM2"))
{
BackHFlip=true;
Params.backHFlip=true;
}
else if(URfSupported.contains("DM4"))
{
BackHFlip=true;
BackVFlip=true;
Params.backHFlip=true;
Params.backVFlip=true;
}
}
}
}
int copies_requested = getAttrOrDefault(jobAttrs, "copies").toInt(1);
QJsonArray varying_attributes = _attrs["document-format-varying-attributes"].toObject()["value"].toArray();
bool supports_copies = _attrs.contains("copies-supported")
&& _attrs["copies-supported"].toObject()["value"].toObject()["high"].toInt(1) != 1
// assume raster formats causes the variation in supported copies
&& !(varying_attributes.contains("copies-supported") || varying_attributes.contains("copies"));
if(copies_requested > 1 && !supports_copies)
{
QString copyMode = getAttrOrDefault(jobAttrs, "multiple-document-handling").toString();
qDebug() << "Doing local copies" << copyMode << copies_requested;
if(copyMode == "separate-documents-uncollated-copies")
{ // Only do silly copies if explicitly requested
Params.pageCopies = copies_requested;
}
else
{
Params.documentCopies = copies_requested;
}
jobAttrs.remove("copies");
if(Sides != "one-sided")
{
bool singlePageRange = false;
if(Params.pageRangeList.size() == 1)
{
size_t fromPage = Params.pageRangeList.begin()->first;
size_t toPage = Params.pageRangeList.begin()->second;
singlePageRange = fromPage != 0 && fromPage == toPage;
}
bool singlePageDocument = (Mimer::instance()->isImage(mimeType) ||
(mimeType == Mimer::PDF && ConvertChecker::instance()->pdfPages(filename) == 1));
if(singlePageDocument || singlePageRange)
{
jobAttrs.insert("sides", QJsonObject {{"tag", IppMsg::Keyword}, {"value", "one-sided"}});
qDebug() << "Optimizing one-page document to one-sided";
}
}
}
}
void IppPrinter::print(QJsonObject jobAttrs, QString filename)
@ -445,6 +593,7 @@ void IppPrinter::print(QJsonObject jobAttrs, QString filename)
_progress = "";
emit progressChanged();
PrintParameters Params;
QFileInfo fileinfo(filename);
if(!fileinfo.exists())
@ -465,22 +614,48 @@ void IppPrinter::print(QJsonObject jobAttrs, QString filename)
qDebug() << supportedMimeTypes << supportedMimeTypes.contains(mimeType);
QJsonObject o = opAttrs();
o.insert("job-name", QJsonObject {{"tag", IppMsg::NameWithoutLanguage}, {"value", fileinfo.fileName()}});
QString PaperSize = getAttrOrDefault(jobAttrs, "media").toString();
if(jobAttrs.contains("media-col") && jobAttrs.contains("media"))
if(!Params.setPaperSize(getAttrOrDefault(jobAttrs, "media").toString(Params.paperSizeName.c_str()).toStdString()))
{
qDebug() << "moving media to media-col" << PaperSize;
if(!PaperSizes.contains(PaperSize))
{
emit convertFailed(tr("Unknown document format dimensions"));
emit convertFailed(tr("Unsupported paper size"));
return;
}
int x = PaperSizes[PaperSize].width()*100;
int y = PaperSizes[PaperSize].height()*100;
QString targetFormat = getAttrOrDefault(jobAttrs, "document-format").toString();
qDebug() << "target format:" << targetFormat;
QMargins margins(getAttrOrDefault(jobAttrs, "media-left-margin", "media-col").toInt(),
getAttrOrDefault(jobAttrs, "media-top-margin", "media-col").toInt(),
getAttrOrDefault(jobAttrs, "media-right-margin", "media-col").toInt(),
getAttrOrDefault(jobAttrs, "media-bottom-margin", "media-col").toInt());
// Only keep margin setting for JPEG - but only attemt to remove it if media-col exists
if(!mimer->isImage(targetFormat) && jobAttrs.contains("media-col"))
{
QJsonObject MediaCol = jobAttrs["media-col"].toObject();
QJsonObject MediaColValue = MediaCol["value"].toObject();
MediaColValue.remove("media-left-margin");
MediaColValue.remove("media-top-margin");
MediaColValue.remove("media-right-margin");
MediaColValue.remove("media-bottom-margin");
if(!MediaColValue.empty())
{
MediaCol["value"] = MediaColValue;
jobAttrs["media-col"] = MediaCol;
}
else
{
jobAttrs.remove("media-col");
}
}
if(jobAttrs.contains("media-col") && jobAttrs.contains("media"))
{
qDebug() << "moving media to media-col" << Params.paperSizeName.c_str();
int x = Params.getPaperSizeWInMillimeters()*100;
int y = Params.getPaperSizeHInMillimeters()*100;
QJsonObject Dimensions =
{{"tag", IppMsg::BeginCollection},
@ -499,125 +674,114 @@ void IppPrinter::print(QJsonObject jobAttrs, QString filename)
jobAttrs.remove("media");
}
QString documentFormat = getAttrOrDefault(jobAttrs, "document-format").toString();
qDebug() << "target format:" << documentFormat;
QJsonObject jobOpAttrs = opAttrs();
// document-format goes in the op-attrs and not the job-attrs
o.insert("document-format", QJsonObject {{"tag", IppMsg::MimeMediaType}, {"value", documentFormat}});
jobOpAttrs.insert("document-format", QJsonObject {{"tag", IppMsg::MimeMediaType}, {"value", targetFormat}});
jobAttrs.remove("document-format");
documentFormat = targetFormatIfAuto(documentFormat, mimeType, supportedMimeTypes);
qDebug() << "adjusted target format:" << documentFormat;
targetFormat = targetFormatIfAuto(targetFormat, mimeType, supportedMimeTypes);
qDebug() << "adjusted target format:" << targetFormat;
if(documentFormat == "" || documentFormat == Mimer::OctetStream)
if(targetFormat == "" || targetFormat == Mimer::OctetStream)
{
emit convertFailed(tr("Unknown document format"));
return;
}
qDebug() << "Printing job" << o << jobAttrs;
QJsonValue PrinterResolutionRef = getAttrOrDefault(jobAttrs, "printer-resolution");
quint32 HwResX = PrinterResolutionRef.toObject()["x"].toInt(300);
quint32 HwResY = PrinterResolutionRef.toObject()["y"].toInt(300);
bool BackHFlip = false;
bool BackVFlip = false;
adjustRasterSettings(documentFormat, jobAttrs, HwResX, HwResY, BackHFlip, BackVFlip);
quint32 Quality = getAttrOrDefault(jobAttrs, "print-quality").toInt();
QString PrintColorMode = getAttrOrDefault(jobAttrs, "print-color-mode").toString();
quint32 Colors = PrintColorMode.contains("color") ? 3 : PrintColorMode.contains("monochrome") ? 1 : 0;
quint32 PageRangeLow = 0;
quint32 PageRangeHigh = 0;
if(jobAttrs.contains("page-ranges"))
if(targetFormat == Mimer::PDF)
{
QJsonObject PageRanges = getAttrOrDefault(jobAttrs, "page-ranges").toObject();
PageRangeLow = PageRanges["low"].toInt();
PageRangeHigh = PageRanges["high"].toInt();
// Effected locally, unless it is Postscript which we cant't render
if(documentFormat != Mimer::Postscript)
{
jobAttrs.remove("page-ranges");
Params.format = PrintParameters::PDF;
}
}
qDebug() << "Final op attributes:" << o;
qDebug() << "Final job attributes:" << jobAttrs;
IppMsg job = mk_msg(o, jobAttrs);
Bytestream contents = job.encode(IppMsg::PrintJob);
// Shouldn't and can't process these formats respectively
if((mimeType == documentFormat) && (documentFormat == Mimer::JPEG || documentFormat == Mimer::Postscript))
else if(targetFormat == Mimer::Postscript)
{
emit doJustUpload(filename, contents);
Params.format = PrintParameters::Postscript;
}
else if(targetFormat == Mimer::PWG)
{
Params.format = PrintParameters::PWG;
}
else if(targetFormat == Mimer::URF)
{
Params.format = PrintParameters::URF;
}
else
{
if(PaperSize == "")
{
PaperSize = "iso_a4_210x297mm";
}
else if(!PaperSizes.contains(PaperSize))
{
emit convertFailed(tr("Unsupported print media"));
return;
Params.format = PrintParameters::Invalid;
}
bool TwoSided = false;
bool Tumble = false;
qDebug() << "Printing job" << jobOpAttrs << jobAttrs;
QJsonValue PrinterResolutionRef = getAttrOrDefault(jobAttrs, "printer-resolution");
Params.hwResW = PrinterResolutionRef.toObject()["x"].toInt(Params.hwResW);
Params.hwResH = PrinterResolutionRef.toObject()["y"].toInt(Params.hwResH);
// Effected locally, unless it is Postscript which we cant't render
if(jobAttrs.contains("page-ranges") && mimeType != Mimer::Postscript)
{
QJsonArray tmp;
QJsonValue pageRanges = getAttrOrDefault(jobAttrs, "page-ranges");
if(pageRanges.isArray())
{
tmp = pageRanges.toArray();
}
else if(pageRanges.isObject())
{
tmp = {pageRanges.toObject()};
}
for(QJsonValueRef ref : tmp)
{
Params.pageRangeList.push_back({ref.toObject()["low"].toInt(), ref.toObject()["high"].toInt()});
}
jobAttrs.remove("page-ranges");
}
adjustRasterSettings(filename, mimeType, jobAttrs, Params);
QString Sides = getAttrOrDefault(jobAttrs, "sides").toString();
if(Sides=="two-sided-long-edge")
{
TwoSided = true;
Params.duplex = true;
}
else if(Sides=="two-sided-short-edge")
{
TwoSided = true;
Tumble = true;
Params.duplex = true;
Params.tumble = true;
}
setBusyMessage(tr("Preparing"));
Params.quality = getAttrOrDefault(jobAttrs, "print-quality").toInt();
if(mimeType == Mimer::PDF)
{
emit doConvertPdf(filename, contents, documentFormat, Colors, Quality,
PaperSize, HwResX, HwResY, TwoSided, Tumble, PageRangeLow, PageRangeHigh,
BackHFlip, BackVFlip);
}
else if(mimeType == Mimer::Plaintext)
{
emit doConvertPlaintext(filename, contents, documentFormat, Colors, Quality,
PaperSize, HwResX, HwResY, TwoSided, Tumble, BackHFlip, BackVFlip);
}
else if (Mimer::isImage(mimeType))
{
QMargins margins(getAttrOrDefault(jobAttrs, "media-left-margin", "media-col").toInt(),
getAttrOrDefault(jobAttrs, "media-top-margin", "media-col").toInt(),
getAttrOrDefault(jobAttrs, "media-right-margin", "media-col").toInt(),
getAttrOrDefault(jobAttrs, "media-bottom-margin", "media-col").toInt());
QString PrintColorMode = getAttrOrDefault(jobAttrs, "print-color-mode").toString();
Params.colors = PrintColorMode.contains("color") ? 3 : PrintColorMode.contains("monochrome") ? 1 : Params.colors;
emit doConvertImage(filename, contents, documentFormat, Colors, Quality,
PaperSize, HwResX, HwResY, margins);
}
else if(Mimer::isOffice(mimeType))
QJsonArray supportedOperations = _attrs["operations-supported"].toObject()["value"].toArray();
if(supportedOperations.contains(IppMsg::CreateJob) && supportedOperations.contains(IppMsg::SendDocument))
{
emit doConvertOfficeDocument(filename, contents, documentFormat, Colors, Quality,
PaperSize, HwResX, HwResY, TwoSided, Tumble, PageRangeLow, PageRangeHigh,
BackHFlip, BackVFlip);
QJsonObject createJobOpAttrs = opAttrs();
createJobOpAttrs.insert("job-name", QJsonObject {{"tag", IppMsg::NameWithoutLanguage}, {"value", fileinfo.fileName()}});
qDebug() << "Final create op attributes:" << createJobOpAttrs;
qDebug() << "Final job attributes:" << jobAttrs;
IppMsg createJob = mk_msg(IppMsg::CreateJob, createJobOpAttrs, jobAttrs);
qDebug() << "Final job op attributes:" << jobOpAttrs;
IppMsg sendDoc = mk_msg(IppMsg::SendDocument, jobOpAttrs);
emit doPrint2(filename, mimeType, targetFormat, createJob, sendDoc, Params, margins);
}
else
{
emit convertFailed(tr("Cannot convert this file format"));
return;
}
jobOpAttrs.insert("job-name", QJsonObject {{"tag", IppMsg::NameWithoutLanguage}, {"value", fileinfo.fileName()}});
qDebug() << "Final op attributes:" << jobOpAttrs;
qDebug() << "Final job attributes:" << jobAttrs;
IppMsg job = mk_msg(IppMsg::PrintJob, jobOpAttrs, jobAttrs);
emit doPrint(filename, mimeType, targetFormat, job, Params, margins);
}
return;
}
bool IppPrinter::getJobs() {
@ -626,9 +790,9 @@ bool IppPrinter::getJobs() {
QJsonObject o = opAttrs();
o.insert("requested-attributes", QJsonObject {{"tag", IppMsg::Keyword}, {"value", "all"}});
IppMsg job = IppMsg(o, QJsonObject());
IppMsg job = IppMsg(IppMsg::GetJobs, o, QJsonObject());
emit doGetJobs(job.encode(IppMsg::GetJobs));
emit doGetJobs(job.encode());
return true;
}
@ -640,16 +804,29 @@ bool IppPrinter::cancelJob(qint32 jobId) {
QJsonObject o = opAttrs();
o.insert("job-id", QJsonObject {{"tag", IppMsg::Integer}, {"value", jobId}});
IppMsg job = IppMsg(o, QJsonObject());
IppMsg job = IppMsg(IppMsg::CancelJob, o, QJsonObject());
emit doCancelJob(job.encode(IppMsg::CancelJob));
emit doCancelJob(job.encode());
return true;
}
bool IppPrinter::identify() {
qDebug() << "identifying";
QJsonObject o = opAttrs();
IppMsg job = IppMsg(IppMsg::IdentifyPrinter, o, QJsonObject());
emit doCancelJob(job.encode());
return true;
}
bool IppPrinter::correctSuffix()
{
foreach(QJsonValue u, _attrs["printer-uri-supported"].toObject()["value"].toArray())
for(QJsonValue u : _attrs["printer-uri-supported"].toObject()["value"].toArray())
{
QUrl url(u.toString());
if(url.path() == _url.path())
@ -663,7 +840,7 @@ bool IppPrinter::correctSuffix()
QStringList IppPrinter::suffixes()
{
QStringList res;
foreach(QJsonValue u, _attrs["printer-uri-supported"].toObject()["value"].toArray())
for(QJsonValue u : _attrs["printer-uri-supported"].toObject()["value"].toArray())
{
QUrl url(u.toString());
if(!res.contains(url.path()))
@ -711,6 +888,13 @@ void IppPrinter::setProgress(qint64 sent, qint64 total)
emit progressChanged();
}
bool IppPrinter::isAllowedAddress(QUrl url)
{
bool allowed = ((url.host() == _url.host()) || Settings::instance()->allowExternalConnections());
qDebug() << url << " is allowed: " << allowed;
return allowed;
}
QJsonValue IppPrinter::getAttrOrDefault(QJsonObject jobAttrs, QString name, QString subkey)
{
if(subkey == "")
@ -738,13 +922,34 @@ QJsonValue IppPrinter::getAttrOrDefault(QJsonObject jobAttrs, QString name, QStr
}
}
IppMsg IppPrinter::mk_msg(QJsonObject opAttrs, QJsonObject jobAttrs)
IppMsg IppPrinter::mk_msg(IppMsg::Operation operation, QJsonObject opAttrs, QJsonObject jobAttrs)
{
if(_attrs.contains("ipp-versions-supported") &&
_attrs["ipp-versions-supported"].toObject()["value"].toArray().contains("2.0"))
{
qDebug() << "TWO-POINT-ZERO";
return IppMsg(opAttrs, jobAttrs, 2, 0);
return IppMsg(operation, opAttrs, jobAttrs, 2, 0);
}
return IppMsg(operation, opAttrs, jobAttrs);
}
void IppPrinter::resolveUrl(QUrl& url)
{
if(!IppDiscovery::instance()->resolve(url))
{ // If "proper" resolution fails, cheat...
QString host = url.host();
if(host.endsWith("."))
{
host.chop(1);
}
QString dnsSdName = _attrs["printer-dns-sd-name"].toObject()["value"].toString();
dnsSdName = dnsSdName.append(".local");
if(host.compare(dnsSdName, Qt::CaseInsensitive) == 0)
{ // This could be done unconditionally, but some might want their externally hosted stuff to work
url.setHost(_url.host());
}
}
return IppMsg(opAttrs, jobAttrs);
}

View file

@ -1,23 +1,24 @@
#ifndef IPPPRINTER_H
#define IPPPRINTER_H
#include <QtNetwork>
#include <QNetworkAccessManager>
#include "ippmsg.h"
#include "convertworker.h"
#include "printerworker.h"
#include "curlrequester.h"
#include <mlite5/MGConfItem>
#include <QImage>
class IppPrinter : public QObject
{
Q_OBJECT
friend class ConvertWorker;
friend class PrinterWorker;
Q_PROPERTY(QString url READ getUrl WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY(QJsonObject attrs MEMBER _attrs NOTIFY attrsChanged)
Q_PROPERTY(QJsonObject jobAttrs MEMBER _jobAttrs NOTIFY jobAttrsChanged)
Q_PROPERTY(QJsonArray jobs MEMBER _jobs NOTIFY jobsChanged)
Q_PROPERTY(QJsonObject strings MEMBER _strings NOTIFY stringsChanged)
Q_PROPERTY(QImage icon MEMBER _icon NOTIFY iconChanged)
Q_PROPERTY(QStringList additionalDocumentFormats MEMBER _additionalDocumentFormats NOTIFY additionalDocumentFormatsChanged)
Q_PROPERTY(QString busyMessage MEMBER _busyMessage NOTIFY busyMessageChanged)
Q_PROPERTY(QString progress MEMBER _progress NOTIFY progressChanged)
@ -37,6 +38,7 @@ public:
Q_INVOKABLE bool getJobs();
Q_INVOKABLE bool cancelJob(qint32 jobId);
Q_INVOKABLE bool identify();
bool correctSuffix();
QStringList suffixes();
@ -47,32 +49,34 @@ signals:
void jobAttrsChanged();
void jobsChanged();
void stringsChanged();
void iconChanged();
void jobFinished(bool status);
void cancelStatus(bool status);
void doCommand(Bytestream msg);
void doDoGetPrinterAttributes(Bytestream msg);
void doGetJobs(Bytestream msg);
void doCancelJob(Bytestream msg);
void doIdentify(Bytestream msg);
void doJustUpload(QString filename, Bytestream header);
void doFixupPlaintext(QString filename, Bytestream header);
void doPrintImageAsImage(QString filename, Bytestream header, QString targetFormat);
void doConvertPdf(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, bool TwoSided, bool Tumble,
quint32 PageRangeLow, quint32 PageRangeHigh, bool BackHFlip, bool BackVFlip);
void doConvertPdf(QString filename, Bytestream header, PrintParameters Params);
void doConvertImage(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, QMargins margins);
void doConvertImage(QString filename, Bytestream header, PrintParameters Params, QMargins margins);
void doConvertOfficeDocument(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, bool TwoSided, bool Tumble,
quint32 PageRangeLow, quint32 PageRangeHigh, bool BackHFlip, bool BackVFlip);
void doConvertOfficeDocument(QString filename, Bytestream header, PrintParameters Params);
void doConvertPlaintext(QString filename, Bytestream header,
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, bool TwoSided, bool Tumble, bool BackHFlip, bool BackVFlip);
void doConvertPlaintext(QString filename, Bytestream header, PrintParameters Params);
void doPrint(QString filename, QString mimeType, QString targetFormat, IppMsg job, PrintParameters Params, QMargins margins);
void doPrint2(QString filename, QString mimeType, QString targetFormat, IppMsg createJob, IppMsg sendDocument, PrintParameters Params, QMargins margins);
void doGetStrings(QUrl url);
void doGetImage(QUrl url);
void additionalDocumentFormatsChanged();
void busyMessageChanged();
@ -81,46 +85,54 @@ signals:
public slots:
void print(QJsonObject attrs, QString file);
void onUrlChanged();
void MaybeGetStrings();
void MaybeGetIcon(bool retry=false);
void UpdateAdditionalDocumentFormats();
void getPrinterAttributesFinished(CURLcode res, Bytestream data);
void printRequestFinished(CURLcode res, Bytestream data);
void getJobsRequestFinished(CURLcode res, Bytestream data);
void cancelJobFinished(CURLcode res, Bytestream data);
void identifyFinished(CURLcode res, Bytestream data);
static void ignoreSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
void getStringsFinished(CURLcode res, Bytestream data);
void getImageFinished(CURLcode res, Bytestream data);
void convertFailed(QString message);
private:
QUrl _url;
QUrl httpUrl();
void resolveUrl(QUrl& url);
QJsonObject opAttrs();
void adjustRasterSettings(QString documentFormat, QJsonObject& jobAttrs, quint32& HwResX, quint32& HwResY,
bool& BackHFlip, bool& BackVFlip);
void adjustRasterSettings(QString filename, QString mimeType, QJsonObject& jobAttrs, PrintParameters& Params);
void setBusyMessage(QString msg);
void setProgress(qint64 sent, qint64 total);
bool isAllowedAddress(QUrl addr);
QJsonValue getAttrOrDefault(QJsonObject jobAttrs, QString name, QString subkey = "");
IppMsg mk_msg(QJsonObject opAttrs, QJsonObject jobAttrs=QJsonObject());
IppMsg mk_msg(IppMsg::Operation operation, QJsonObject opAttrs, QJsonObject jobAttrs=QJsonObject());
QJsonObject _attrs;
QJsonObject _jobAttrs;
QJsonArray _jobs;
QJsonObject _strings;
QImage _icon;
QStringList _additionalDocumentFormats;
QString _busyMessage;
QString _progress;
QThread _workerThread;
ConvertWorker* _worker;
PrinterWorker* _worker;
bool _iconRetried = false;
};
#endif // IPPPRINTER_H

View file

@ -1,4 +1,5 @@
#include "mimer.h"
#include <QDebug>
const QString Mimer::OctetStream = "application/octet-stream";
@ -11,16 +12,22 @@ const QString Mimer::PNG = "image/png";
const QString Mimer::GIF = "image/gif";
const QString Mimer::JPEG = "image/jpeg";
const QString Mimer::TIFF = "image/tiff";
const QString Mimer::SVG = "image/svg+xml";
const QString Mimer::RBMP = "image/reverse-encoding-bmp";
const QString Mimer::DOC = "application/msword";
const QString Mimer::DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
const QString Mimer::RTF = "text/rtf";
const QString Mimer::RTF_APP = "application/rtf";
const QString Mimer::ODT = "application/vnd.oasis.opendocument.text";
const QString Mimer::PPT = "application/vnd.ms-powerpoint";
const QString Mimer::PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
const QString Mimer::ODP = "application/vnd.oasis.opendocument.presentation";
const QString Mimer::Plaintext = "text/plain";
const QStringList Mimer::OfficeFormats = {DOC, DOCX, RTF, RTF_APP, ODT};
const QStringList Mimer::RasterFormats = {PWG, URF};
const QStringList Mimer::OfficeFormats = {DOC, DOCX, RTF, RTF_APP, ODT, PPT, PPTX, ODP};
Mimer::Mimer()
{
@ -46,5 +53,14 @@ Mimer* Mimer::instance()
}
QString Mimer::get_type(QString filename) {
return _db.mimeTypeForFile(filename).name();
QString type = _db.mimeTypeForFile(filename).name();
qDebug() << "MimeType:" << type;
if(type == PDF || type == Postscript || type == Plaintext || isImage(type) || isOffice(type))
{
return type;
}
else
{
return "";
}
}

View file

@ -24,11 +24,17 @@ public:
Q_PROPERTY(const QString GIF MEMBER GIF CONSTANT);
Q_PROPERTY(const QString JPEG MEMBER JPEG CONSTANT);
Q_PROPERTY(const QString TIFF MEMBER TIFF CONSTANT);
Q_PROPERTY(const QString SVG MEMBER SVG CONSTANT);
Q_PROPERTY(const QString RBMP MEMBER RBMP CONSTANT);
Q_PROPERTY(const QString DOC MEMBER DOC CONSTANT);
Q_PROPERTY(const QString DOCX MEMBER DOCX CONSTANT);
Q_PROPERTY(const QString RTF MEMBER RTF CONSTANT);
Q_PROPERTY(const QString ODT MEMBER ODT CONSTANT);
Q_PROPERTY(const QString PPT MEMBER PPT CONSTANT);
Q_PROPERTY(const QString PPTX MEMBER PPTX CONSTANT);
Q_PROPERTY(const QString ODP MEMBER ODP CONSTANT);
Q_PROPERTY(const QString Plaintext MEMBER Plaintext CONSTANT);
@ -45,20 +51,30 @@ public:
static const QString GIF;
static const QString JPEG;
static const QString TIFF;
static const QString SVG;
static const QString RBMP;
static const QString DOC;
static const QString DOCX;
static const QString RTF;
static const QString RTF_APP;
static const QString ODT;
static const QString PPT;
static const QString PPTX;
static const QString ODP;
static const QString Plaintext;
static const QStringList RasterFormats;
static const QStringList OfficeFormats;
Q_INVOKABLE static bool isImage(QString mimeType)
{
return mimeType.startsWith("image/");
return mimeType.startsWith("image/") && ! isRaster(mimeType);
}
Q_INVOKABLE static bool isRaster(QString mimeType)
{
return RasterFormats.contains(mimeType);
}
Q_INVOKABLE static bool isOffice(QString mimeType)
{

View file

@ -40,7 +40,7 @@ Overrider* Overrider::instance()
bool Overrider::apply(QJsonObject& attrs)
{
bool applied = false;
foreach(const QString& key, _overrides.keys())
for(const QString& key : _overrides.keys())
{
if(attrs.contains(key))
{
@ -48,7 +48,7 @@ bool Overrider::apply(QJsonObject& attrs)
if(match != "" && _overrides[key].toObject().contains(match))
{
QJsonObject overrideden_data = _overrides[key].toObject()[match].toObject();
foreach(const QString& o_key, overrideden_data.keys())
for(const QString& o_key : overrideden_data.keys())
{
attrs.insert(o_key, overrideden_data[o_key]);
}

View file

@ -3,208 +3,6 @@
#include <QMap>
#include <QSizeF>
static QMap<QString, QSizeF> PaperSizes =
{{"asme_f_28x40in", {711.20, 1016.00}},
{"iso_2a0_1189x1682mm", {1189.00, 1682.00}},
{"iso_a0_841x1189mm", {841.00, 1189.00}},
{"iso_a0x3_1189x2523mm", {1189.00, 2523.00}},
{"iso_a10_26x37mm", {26.00, 37.00}},
{"iso_a1_594x841mm", {594.00, 841.00}},
{"iso_a1x3_841x1783mm", {841.00, 1783.00}},
{"iso_a1x4_841x2378mm", {841.00, 2378.00}},
{"iso_a2_420x594mm", {420.00, 594.00}},
{"iso_a2x3_594x1261mm", {594.00, 1261.00}},
{"iso_a2x4_594x1682mm", {594.00, 1682.00}},
{"iso_a2x5_594x2102mm", {594.00, 2102.00}},
{"iso_a3-extra_322x445mm", {322.00, 445.00}},
{"iso_a3_297x420mm", {297.00, 420.00}},
{"iso_a3x3_420x891mm", {420.00, 891.00}},
{"iso_a3x4_420x1189mm", {420.00, 1189.00}},
{"iso_a3x5_420x1486mm", {420.00, 1486.00}},
{"iso_a3x6_420x1783mm", {420.00, 1783.00}},
{"iso_a3x7_420x2080mm", {420.00, 2080.00}},
{"iso_a4-extra_235.5x322.3m", {235.50, 322.30}},
{"iso_a4-tab_225x297mm", {225.00, 297.00}},
{"iso_a4_210x297mm", {210.00, 297.00}},
{"iso_a4x3_297x630mm", {297.00, 630.00}},
{"iso_a4x4_297x841mm", {297.00, 841.00}},
{"iso_a4x5_297x1051mm", {297.00, 1051.00}},
{"iso_a4x6_297x1261mm", {297.00, 1261.00}},
{"iso_a4x7_297x1471mm", {297.00, 1471.00}},
{"iso_a4x8_297x1682mm", {297.00, 1682.00}},
{"iso_a4x9_297x1892mm", {297.00, 1892.00}},
{"iso_a5-extra_174x235mm", {174.00, 235.00}},
{"iso_a5_148x210mm", {148.00, 210.00}},
{"iso_a6_105x148mm", {105.00, 148.00}},
{"iso_a7_74x105mm", {74.00, 105.00}},
{"iso_a8_52x74mm", {52.00, 74.00}},
{"iso_a9_37x52mm", {37.00, 52.00}},
{"iso_b0_1000x1414mm", {1000.00, 1414.00}},
{"iso_b10_31x44mm", {31.00, 44.00}},
{"iso_b1_707x1000mm", {707.00, 1000.00}},
{"iso_b2_500x707mm", {500.00, 707.00}},
{"iso_b3_353x500mm", {353.00, 500.00}},
{"iso_b4_250x353mm", {250.00, 353.00}},
{"iso_b5-extra_201x276mm", {201.00, 276.00}},
{"iso_b5_176x250mm", {176.00, 250.00}},
{"iso_b6_125x176mm", {125.00, 176.00}},
{"iso_b6c4_125x324mm", {125.00, 324.00}},
{"iso_b7_88x125mm", {88.00, 125.00}},
{"iso_b8_62x88mm", {62.00, 88.00}},
{"iso_b9_44x62mm", {44.00, 62.00}},
{"iso_c0_917x1297mm", {917.00, 1297.00}},
{"iso_c10_28x40mm", {28.00, 40.00}},
{"iso_c1_648x917mm", {648.00, 917.00}},
{"iso_c2_458x648mm", {458.00, 648.00}},
{"iso_c3_324x458mm", {324.00, 458.00}},
{"iso_c4_229x324mm", {229.00, 324.00}},
{"iso_c5_162x229mm", {162.00, 229.00}},
{"iso_c6_114x162mm", {114.00, 162.00}},
{"iso_c6c5_114x229mm", {114.00, 229.00}},
{"iso_c7_81x114mm", {81.00, 114.00}},
{"iso_c7c6_81x162mm", {81.00, 162.00}},
{"iso_c8_57x81mm", {57.00, 81.00}},
{"iso_c9_40x57mm", {40.00, 57.00}},
{"iso_dl_110x220mm", {110.00, 220.00}},
{"iso_id-1_53.98x85.6mm", {53.98, 85.60}},
{"iso_id-3_88x125mm", {88.00, 125.00}},
{"iso_ra0_860x1220mm", {860.00, 1220.00}},
{"iso_ra1_610x860mm", {610.00, 860.00}},
{"iso_ra2_430x610mm", {430.00, 610.00}},
{"iso_ra3_305x430mm", {305.00, 430.00}},
{"iso_ra4_215x305mm", {215.00, 305.00}},
{"iso_sra0_900x1280mm", {900.00, 1280.00}},
{"iso_sra1_640x900mm", {640.00, 900.00}},
{"iso_sra2_450x640mm", {450.00, 640.00}},
{"iso_sra3_320x450mm", {320.00, 450.00}},
{"iso_sra4_225x320mm", {225.00, 320.00}},
{"jis_b0_1030x1456mm", {1030.00, 1456.00}},
{"jis_b10_32x45mm", {32.00, 45.00}},
{"jis_b1_728x1030mm", {728.00, 1030.00}},
{"jis_b2_515x728mm", {515.00, 728.00}},
{"jis_b3_364x515mm", {364.00, 515.00}},
{"jis_b4_257x364mm", {257.00, 364.00}},
{"jis_b5_182x257mm", {182.00, 257.00}},
{"jis_b6_128x182mm", {128.00, 182.00}},
{"jis_b7_91x128mm", {91.00, 128.00}},
{"jis_b8_64x91mm", {64.00, 91.00}},
{"jis_b9_45x64mm", {45.00, 64.00}},
{"jis_exec_216x330mm", {216.00, 330.00}},
{"jpn_chou2_111.1x146mm", {111.10, 146.00}},
{"jpn_chou3_120x235mm", {120.00, 235.00}},
{"jpn_chou40_90x225mm", {90.00, 225.00}},
{"jpn_chou4_90x205mm", {90.00, 205.00}},
{"jpn_hagaki_100x148mm", {100.00, 148.00}},
{"jpn_kahu_240x322.1mm", {240.00, 322.10}},
{"jpn_kaku1_270x382mm", {270.00, 382.00}},
{"jpn_kaku2_240x332mm", {240.00, 332.00}},
{"jpn_kaku3_216x277mm", {216.00, 277.00}},
{"jpn_kaku4_197x267mm", {197.00, 267.00}},
{"jpn_kaku5_190x240mm", {190.00, 240.00}},
{"jpn_kaku7_142x205mm", {142.00, 205.00}},
{"jpn_kaku8_119x197mm", {119.00, 197.00}},
{"jpn_oufuku_148x200mm", {148.00, 200.00}},
{"jpn_you4_105x235mm", {105.00, 235.00}},
{"na_10x11_10x11in", {254.00, 279.40}},
{"na_10x13_10x13in", {254.00, 330.20}},
{"na_10x14_10x14in", {254.00, 355.60}},
{"na_10x15_10x15in", {254.00, 381.00}},
{"na_11x12_11x12in", {279.40, 304.80}},
{"na_11x15_11x15in", {279.40, 381.00}},
{"na_12x19_12x19in", {304.80, 482.60}},
{"na_5x7_5x7in", {127.00, 177.80}},
{"na_6x9_6x9in", {152.40, 228.60}},
{"na_7x9_7x9in", {177.80, 228.60}},
{"na_9x11_9x11in", {228.60, 279.40}},
{"na_a2_4.375x5.75in", {111.12, 146.05}},
{"na_arch-a_9x12in", {228.60, 304.80}},
{"na_arch-b_12x18in", {304.80, 457.20}},
{"na_arch-c_18x24in", {457.20, 609.60}},
{"na_arch-d_24x36in", {609.60, 914.40}},
{"na_arch-e2_26x38in", {660.40, 965.20}},
{"na_arch-e3_27x39in", {685.80, 990.60}},
{"na_arch-e_36x48in", {914.40, 1219.20}},
{"na_b-plus_12x19.17in", {304.80, 486.92}},
{"na_c5_6.5x9.5in", {165.10, 241.30}},
{"na_c_17x22in", {431.80, 558.80}},
{"na_d_22x34in", {558.80, 863.60}},
{"na_e_34x44in", {863.60, 1117.60}},
{"na_edp_11x14in", {279.40, 355.60}},
{"na_eur-edp_12x14in", {304.80, 355.60}},
{"na_executive_7.25x10.5in", {184.15, 266.70}},
{"na_f_44x68in", {1117.60, 1727.20}},
{"na_fanfold-eur_8.5x12in", {215.90, 304.80}},
{"na_fanfold-us_11x14.875in", {279.40, 377.82}},
{"na_foolscap_8.5x13in", {215.90, 330.20}},
{"na_govt-legal_8x13in", {203.20, 330.20}},
{"na_govt-letter_8x10in", {203.20, 254.00}},
{"na_index-3x5_3x5in", {76.20, 127.00}},
{"na_index-4x6-ext_6x8in", {152.40, 203.20}},
{"na_index-4x6_4x6in", {101.60, 152.40}},
{"na_index-5x8_5x8in", {127.00, 203.20}},
{"na_invoice_5.5x8.5in", {139.70, 215.90}},
{"na_ledger_11x17in", {279.40, 431.80}},
{"na_legal-extra_9.5x15in", {241.30, 381.00}},
{"na_legal_8.5x14in", {215.90, 355.60}},
{"na_letter-extra_9.5x12in", {241.30, 304.80}},
{"na_letter-plus_8.5x12.69in", {215.90, 322.33}},
{"na_letter_8.5x11in", {215.90, 279.40}},
{"na_monarch_3.875x7.5in", {98.42, 190.50}},
{"na_number-10_4.125x9.5in", {104.77, 241.30}},
{"na_number-11_4.5x10.375in", {114.30, 263.52}},
{"na_number-12_4.75x11in", {120.65, 279.40}},
{"na_number-14_5x11.5in", {127.00, 292.10}},
{"na_number-9_3.875x8.875in", {98.42, 225.42}},
{"na_oficio_8.5x13.4in", {215.90, 340.36}},
{"na_personal_3.625x6.5in", {92.07, 165.10}},
{"na_quarto_8.5x10.83in", {215.90, 275.08}},
{"na_super-a_8.94x14in", {227.08, 355.60}},
{"na_super-b_13x19in", {330.20, 482.60}},
{"na_wide-format_30x42in", {762.00, 1066.80}},
{"oe_12x16_12x16in", {304.80, 406.40}},
{"oe_14x17_14x17in", {355.60, 431.80}},
{"oe_18x22_18x22in", {457.20, 558.80}},
{"oe_a2plus_17x24in", {431.80, 609.60}},
{"oe_business-card_2x3.5in", {50.80, 88.90}},
{"oe_photo-10r_10x12in", {254.00, 304.80}},
{"oe_photo-20r_20x24in", {508.00, 609.60}},
{"oe_photo-l_3.5x5in", {88.90, 127.00}},
{"oe_photo-s10r_10x15in", {254.00, 381.00}},
{"oe_square-photo_4x4in", {101.60, 101.60}},
{"oe_square-photo_5x5in", {127.00, 127.00}},
{"om_16k_184x260mm", {184.00, 260.00}},
{"om_16k_195x270mm", {195.00, 270.00}},
{"om_business-card_55x85mm", {55.00, 85.00}},
{"om_business-card_55x91mm", {55.00, 91.00}},
{"om_card_54x86mm", {54.00, 86.00}},
{"om_dai-pa-kai_275x395mm", {275.00, 395.00}},
{"om_dsc-photo_89x119mm", {89.00, 119.00}},
{"om_folio-sp_215x315mm", {215.00, 315.00}},
{"om_folio_210x330mm", {210.00, 330.00}},
{"om_invite_220x220mm", {220.00, 220.00}},
{"om_italian_110x230mm", {110.00, 230.00}},
{"om_juuro-ku-kai_198x275mm", {198.00, 275.00}},
{"om_large-photo_200x300", {200.00, 300.00}},
{"om_medium-photo_130x180mm", {130.00, 180.00}},
{"om_pa-kai_267x389mm", {267.00, 389.00}},
{"om_postfix_114x229mm", {114.00, 229.00}},
{"om_small-photo_100x150mm", {100.00, 150.00}},
{"om_square-photo_89x89mm", {89.00, 89.00}},
{"om_wide-photo_100x200mm", {100.00, 200.00}},
{"prc_10_324x458mm", {324.00, 458.00}},
{"prc_16k_146x215mm", {146.00, 215.00}},
{"prc_1_102x165mm", {102.00, 165.00}},
{"prc_2_102x176mm", {102.00, 176.00}},
{"prc_32k_97x151mm", {97.00, 151.00}},
{"prc_3_125x176mm", {125.00, 176.00}},
{"prc_4_110x208mm", {110.00, 208.00}},
{"prc_5_110x220mm", {110.00, 220.00}},
{"prc_6_120x320mm", {120.00, 320.00}},
{"prc_7_160x230mm", {160.00, 230.00}},
{"prc_8_120x309mm", {120.00, 309.00}},
{"roc_16k_7.75x10.75in", {196.85, 273.05}},
{"roc_8k_10.75x15.5in", {273.05, 393.70}}};
static QMap<QString, QString> CalligraPaperSizes =
{{"iso_a0_841x1189mm", "A0"},
{"iso_a1_594x841mm", "A1"},

812
src/printerworker.cpp Normal file
View file

@ -0,0 +1,812 @@
#include "printerworker.h"
#include "papersizes.h"
#include "convertchecker.h"
#include "mimer.h"
#include <QImage>
#include <QMatrix>
#include <QPainter>
#include <QTextDocument>
#include <QPdfWriter>
#include <QAbstractTextDocumentLayout>
#include <QtSvg>
#include "ippprinter.h"
#include "pdf2printable.h"
#include "ppm2pwg.h"
#include "baselinify.h"
#include <fstream>
#include <iostream>
#define OK(call) if(!(call)) throw ConvertFailedException()
PrinterWorker::PrinterWorker(IppPrinter* parent)
{
_printer = parent;
_url = parent->httpUrl();
_thread.reset(new QThread);
moveToThread(_thread.get());
_thread->start();
}
PrinterWorker::~PrinterWorker()
{
QMetaObject::invokeMethod(this, "cleanup");
_thread->wait();
}
void PrinterWorker::cleanup()
{
_thread->quit();
}
void PrinterWorker::urlChanged(QUrl url)
{
_url = url;
}
void PrinterWorker::getStrings(QUrl url)
{
CurlRequester cr(url, CurlRequester::HttpGetRequest);
awaitResult(cr, "getStringsFinished");
}
void PrinterWorker::getImage(QUrl url)
{
CurlRequester cr(url, CurlRequester::HttpGetRequest);
awaitResult(cr, "getImageFinished");
}
void PrinterWorker::getPrinterAttributes(Bytestream msg)
{
CurlRequester cr(_url, CurlRequester::IppRequest, &msg);
awaitResult(cr, "getPrinterAttributesFinished");
}
void PrinterWorker::getJobs(Bytestream msg)
{
CurlRequester cr(_url, CurlRequester::IppRequest, &msg);
awaitResult(cr, "getJobsRequestFinished");
}
void PrinterWorker::cancelJob(Bytestream msg)
{
CurlRequester cr(_url, CurlRequester::IppRequest, &msg);
awaitResult(cr, "cancelJobFinished");
}
void PrinterWorker::identify(Bytestream msg)
{
CurlRequester cr(_url, CurlRequester::IppRequest, &msg);
awaitResult(cr, "identifyFinished");
}
void PrinterWorker::print2(QString filename, QString mimeType, QString targetFormat, IppMsg createJob, IppMsg sendDocument, PrintParameters Params, QMargins margins)
{
emit busyMessage(tr("Preparing"));
Bytestream header = createJob.encode();
CurlRequester cr(_url, CurlRequester::IppRequest, &header);
Bytestream resData;
CURLcode res = cr.await(&resData);
if(res == CURLE_OK)
{
IppMsg resMsg(resData);
qDebug() << resMsg.getOpAttrs() << resMsg.getJobAttrs();
QJsonObject resJobAttrs = resMsg.getJobAttrs()[0].toObject();
if(resMsg.getStatus() <= 0xff && resJobAttrs.contains("job-id"))
{
int jobId = resJobAttrs["job-id"].toObject()["value"].toInt();
sendDocument.setOpAttr("job-id", IppMsg::Integer, jobId);
sendDocument.setOpAttr("last-document", IppMsg::Boolean, true);
print(filename, mimeType, targetFormat, sendDocument, Params, margins);
}
else
{
resData.setPos(0);
QMetaObject::invokeMethod(_printer, "printRequestFinished", Qt::QueuedConnection,
Q_ARG(CURLcode, res),
Q_ARG(Bytestream, resData));
}
}
else
{
QMetaObject::invokeMethod(_printer, "printRequestFinished", Qt::QueuedConnection,
Q_ARG(CURLcode, res),
Q_ARG(Bytestream, resData));
}
}
void PrinterWorker::print(QString filename, QString mimeType, QString targetFormat, IppMsg job, PrintParameters Params, QMargins margins)
{
try
{
Mimer* mimer = Mimer::instance();
Bytestream contents = job.encode();
emit busyMessage(tr("Preparing"));
if((mimeType == targetFormat) && (targetFormat == Mimer::Postscript))
{ // Can't process Postscript
justUpload(filename, contents);
}
else if((mimeType == targetFormat) && (targetFormat == Mimer::Plaintext))
{
fixupPlaintext(filename, contents);
}
else if((mimeType != Mimer::SVG) && mimer->isImage(mimeType) && mimer->isImage(targetFormat))
{ // Just make sure the image is in the desired format (and jpeg baseline-encoded), don't resize locally
printImageAsImage(filename, contents, targetFormat);
}
else if(Params.format != PrintParameters::Invalid) // Params.format can be trusted
{
if(mimeType == Mimer::PDF)
{
convertPdf(filename, contents, Params);
}
else if(mimeType == Mimer::Plaintext)
{
if(Params.paperSizeH == 0 && Params.getPaperSizeWInMillimeters() < 26)
{
convertPlaintextLabel(filename, contents, Params);
}
else
{
convertPlaintext(filename, contents, Params);
}
}
else if(Mimer::isImage(mimeType))
{
convertImage(filename, contents, Params, margins);
}
else if(Mimer::isOffice(mimeType))
{
convertOfficeDocument(filename, contents, Params);
}
else
{
emit failed(tr("Cannot convert this file format"));
}
}
else
{
emit failed(tr("Cannot convert this file format"));
}
return;
}
catch(const ConvertFailedException& e)
{
emit failed(e.what() == QString("") ? tr("Print error") : e.what());
}
}
void PrinterWorker::justUpload(QString filename, Bytestream header)
{
emit busyMessage(tr("Printing"));
CurlRequester cr(_url);
QFile file(filename);
file.open(QFile::ReadOnly);
OK(cr.write((char*)header.raw(), header.size()));
QByteArray tmp = file.readAll();
OK(cr.write(tmp.data(), tmp.length()));
file.close();
awaitResult(cr, "printRequestFinished");
}
void PrinterWorker::printImageAsImage(QString filename, Bytestream header, QString targetFormat)
{
QString imageFormat = "";
QStringList supportedImageFormats = {Mimer::JPEG, Mimer::PNG};
if(targetFormat == Mimer::RBMP)
{
// ok
}
else if(supportedImageFormats.contains(targetFormat))
{
imageFormat = targetFormat.split("/")[1];
}
else
{
throw ConvertFailedException(tr("Unknown target format"));
}
QString mimeType = Mimer::instance()->get_type(filename);
Bytestream OutBts;
CurlRequester cr(_url);
if(mimeType == Mimer::JPEG && targetFormat == Mimer::JPEG)
{
std::ifstream ifs = std::ifstream(filename.toStdString(), std::ios::in | std::ios::binary);
Bytestream InBts(ifs);
baselinify(InBts, OutBts);
}
else if(targetFormat == Mimer::RBMP)
{
QImageReader reader(filename);
reader.setAutoTransform(true);
QImage inImage = reader.read();
QBuffer buf;
if(inImage.isNull())
{
qDebug() << "failed to load";
throw ConvertFailedException(tr("Failed to load image"));
}
// TODO: calculate paper width minus margins
// (depends on understanding/parsing custom paper sizes)
int width = 576;
int height = inImage.height() * ((width*1.0)/inImage.width());
inImage = inImage.scaled(width, height);
inImage = inImage.convertToFormat(QImage::Format_Mono);
inImage = inImage.transformed(QMatrix().scale(1,-1));
buf.open(QIODevice::ReadWrite);
inImage.save(&buf, "bmp");
buf.seek(0);
OutBts = Bytestream(buf.size());
buf.read((char*)(OutBts.raw()), buf.size());
}
else if(targetFormat == mimeType)
{
std::ifstream ifs = std::ifstream(filename.toStdString(), std::ios::in | std::ios::binary);
OutBts = Bytestream(ifs);
}
else
{
QImageReader reader(filename);
reader.setAutoTransform(true);
QImage inImage = reader.read();
QBuffer buf;
if(inImage.isNull())
{
qDebug() << "failed to load";
throw ConvertFailedException(tr("Failed to load image"));
}
buf.open(QIODevice::ReadWrite);
inImage.save(&buf, imageFormat.toStdString().c_str());
buf.seek(0);
OutBts = Bytestream(buf.size());
buf.read((char*)(OutBts.raw()), buf.size());
}
emit busyMessage(tr("Printing"));
OK(cr.write((char*)header.raw(), header.size()));
OK(cr.write((char*)OutBts.raw(), OutBts.size()));
awaitResult(cr, "printRequestFinished");
}
void PrinterWorker::fixupPlaintext(QString filename, Bytestream header)
{
CurlRequester cr(_url);
QFile inFile(filename);
if(!inFile.open(QIODevice::ReadOnly))
{
throw ConvertFailedException(tr("Failed to open file"));
}
QString allText = inFile.readAll();
if(allText.startsWith("\f"))
{
allText.remove(0, 1);
}
if(allText.endsWith("\f"))
{
allText.chop(1);
}
else if(allText.endsWith("\f\n"))
{
allText.chop(2);
}
QStringList lines;
for(QString rnline : allText.split("\r\n"))
{
lines.append(rnline.split("\n"));
}
QByteArray outData = lines.join("\r\n").toUtf8();
OK(cr.write((char*)header.raw(), header.size()));
OK(cr.write(outData.data(), outData.length()));
awaitResult(cr, "printRequestFinished");
}
void PrinterWorker::convertPdf(QString filename, Bytestream header, PrintParameters Params)
{
emit busyMessage(tr("Printing"));
CurlRequester cr(_url);
OK(cr.write((char*)header.raw(), header.size()));
write_fun WriteFun([&cr](unsigned char const* buf, unsigned int len) -> bool
{
if(len == 0)
return true;
return cr.write((const char*)buf, len);
});
progress_fun ProgressFun([this](size_t page, size_t total) -> void
{
emit progress(page, total);
});
bool verbose = QLoggingCategory::defaultCategory()->isDebugEnabled();
int res = pdf_to_printable(filename.toStdString(), WriteFun, Params, ProgressFun, verbose);
if(res != 0)
{
throw ConvertFailedException(tr("Conversion failed"));
}
awaitResult(cr, "printRequestFinished");
qDebug() << "Finished";
}
void PrinterWorker::convertImage(QString filename, Bytestream header, PrintParameters Params, QMargins margins)
{
QString mimeType = Mimer::instance()->get_type(filename);
if(Params.format == PrintParameters::URF && (Params.hwResW != Params.hwResH))
{ // URF only supports symmetric resolutions
qDebug() << "Unsupported URF resolution";
throw ConvertFailedException(tr("Unsupported resolution (dpi)"));
}
qDebug() << "Size is" << Params.getPaperSizeWInPixels() << "x" << Params.getPaperSizeHInPixels();
int leftMarginPx = (margins.left()/2540.0)*Params.hwResW;
int rightMarginPx = (margins.right()/2540.0)*Params.hwResW;
int topMarginPx = (margins.top()/2540.0)*Params.hwResH;
int bottomMarginPx = (margins.bottom()/2540.0)*Params.hwResH;
int totalXMarginPx = leftMarginPx+rightMarginPx;
int totalYMarginPx = topMarginPx+bottomMarginPx;
size_t targetWidth = Params.getPaperSizeWInPixels()-totalXMarginPx;
size_t targetHeight = Params.getPaperSizeHInPixels()-totalYMarginPx;
QImage inImage;
if(mimeType == Mimer::SVG)
{
QSvgRenderer renderer(filename);
if(!renderer.isValid())
{
qDebug() << "failed to load svg";
throw ConvertFailedException(tr("Failed to load image"));
}
QSize defaultSize = renderer.defaultSize();
QSize targetSize(targetWidth, targetHeight);
if(defaultSize.width() > defaultSize.height())
{
targetSize.transpose();
}
QSize initialSize = defaultSize.scaled(targetSize, Qt::KeepAspectRatio);
inImage = QImage(initialSize, QImage::Format_RGB32);
inImage.fill(QColor("white"));
QPainter painter(&inImage);
renderer.render(&painter);
painter.end(); // Or else the painter segfaults on destruction if we have messed with the image
if(inImage.width() > inImage.height())
{
inImage = inImage.transformed(QMatrix().rotate(270.0));
}
}
else
{
QImageReader reader(filename);
reader.setAutoTransform(true);
inImage = reader.read();
if(inImage.isNull())
{
qDebug() << "failed to load";
throw ConvertFailedException(tr("Failed to load image"));
}
if(inImage.width() > inImage.height())
{
inImage = inImage.transformed(QMatrix().rotate(270.0));
}
inImage = inImage.scaled(targetWidth, targetHeight,
Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
if(Params.format == PrintParameters::PDF || Params.format == PrintParameters::Postscript)
{
QTemporaryFile tmpPdfFile;
tmpPdfFile.open();
QPdfWriter pdfWriter(tmpPdfFile.fileName());
pdfWriter.setCreator("SeaPrint " SEAPRINT_VERSION);
QPageSize pageSize({Params.getPaperSizeWInPoints(), Params.getPaperSizeHInPoints()}, QPageSize::Point);
pdfWriter.setPageSize(pageSize);
pdfWriter.setResolution(Params.hwResH);
QPainter painter(&pdfWriter);
int xOffset = ((pdfWriter.width()-totalXMarginPx)-inImage.width())/2 + leftMarginPx;
int yOffset = ((pdfWriter.height()-totalYMarginPx)-inImage.height())/2 + topMarginPx;
painter.drawImage(xOffset, yOffset, inImage);
painter.end();
convertPdf(tmpPdfFile.fileName(), header, Params);
}
else
{
size_t total_pages = Params.documentCopies*Params.pageCopies;
if(total_pages > 1 && Params.duplex)
{ // Images are one page by definition - if we need to do client-side copies, they must be one-sided or we'd have to insert backsides
qDebug() << "Inconsistent duplex setting";
throw ConvertFailedException(tr("Inconsistent duplex setting"));
}
QImage outImage = QImage(Params.getPaperSizeWInPixels(), Params.getPaperSizeHInPixels(), inImage.format());
outImage.fill(Qt::white);
QPainter painter(&outImage);
int xOffset = ((outImage.width()-totalXMarginPx)-inImage.width())/2 + leftMarginPx;
int yOffset = ((outImage.height()-totalYMarginPx)-inImage.height())/2 + topMarginPx;
painter.drawImage(xOffset, yOffset, inImage);
painter.end();
QBuffer buf;
buf.open(QIODevice::ReadWrite);
Bytestream fileHdr, outBts;
if(inImage.allGray())
{
Params.colors = 1; // No need to waste space/bandwidth...
}
outImage.save(&buf, Params.colors==1 ? "pgm" : "ppm");
buf.seek(0);
// Skip header - TODO consider reimplementing
buf.readLine(255);
buf.readLine(255);
buf.readLine(255);
Bytestream inBts(Params.getPaperSizeWInPixels() * Params.getPaperSizeHInPixels() * Params.colors);
if((((size_t)buf.size())-buf.pos()) != inBts.size())
{
qDebug() << buf.size() << buf.pos() << inBts.size();
throw ConvertFailedException();
}
buf.read((char*)(inBts.raw()), inBts.size());
fileHdr << (Params.format == PrintParameters::URF ? make_urf_file_hdr(1) : make_pwg_file_hdr());
bool verbose = QLoggingCategory::defaultCategory()->isDebugEnabled();
bmp_to_pwg(inBts, outBts, 1, Params, verbose);
CurlRequester cr(_url);
emit busyMessage(tr("Printing"));
OK(cr.write((char*)header.raw(), header.size()));
OK(cr.write((char*)fileHdr.raw(), fileHdr.size()));
for(size_t c = 0; c < total_pages; c++)
{
OK(cr.write((char*)(outBts.raw()), outBts.size()));
emit progress(c+1, total_pages);
}
awaitResult(cr, "printRequestFinished");
}
qDebug() << "posted";
}
void PrinterWorker::convertOfficeDocument(QString filename, Bytestream header, PrintParameters Params)
{
if(Params.format == PrintParameters::URF && (Params.hwResW != Params.hwResH))
{ // URF only supports symmetric resolutions
qDebug() << "Unsupported URF resolution";
throw ConvertFailedException(tr("Unsupported resolution (dpi)"));
}
QString ShortPaperSize;
if(CalligraPaperSizes.contains(Params.paperSizeName.c_str()))
{
ShortPaperSize = CalligraPaperSizes[Params.paperSizeName.c_str()];
}
else
{
qDebug() << "Unsupported PDF paper size" << Params.paperSizeName.c_str();
throw ConvertFailedException(tr("Unsupported PDF paper size"));
}
QProcess CalligraConverter(this);
CalligraConverter.setProgram("calligraconverter");
QStringList CalligraConverterArgs = {"--batch", "--mimetype", Mimer::PDF, "--print-orientation", "Portrait", "--print-papersize", ShortPaperSize};
CalligraConverterArgs << filename;
QTemporaryFile tmpPdfFile;
tmpPdfFile.open();
CalligraConverterArgs << tmpPdfFile.fileName();
qDebug() << "CalligraConverteArgs is" << CalligraConverterArgs;
CalligraConverter.setArguments(CalligraConverterArgs);
CalligraConverter.start();
qDebug() << "CalligraConverter Starting";
if(!CalligraConverter.waitForStarted())
{
qDebug() << "CalligraConverter died";
throw ConvertFailedException();
}
qDebug() << "CalligraConverter Started";
if(!CalligraConverter.waitForFinished(-1))
{
qDebug() << "CalligraConverter failed";
throw ConvertFailedException();
}
// qDebug() << CalligraConverter->readAllStandardError();
convertPdf(tmpPdfFile.fileName(), header, Params);
qDebug() << "posted";
}
void PrinterWorker::convertPlaintext(QString filename, Bytestream header, PrintParameters Params)
{
QPageSize pageSize;
if(Params.paperSizeUnits == PrintParameters::Millimeters)
{
pageSize = QPageSize(QSizeF {Params.paperSizeW, Params.paperSizeH}, QPageSize::Millimeter);
}
else if(Params.paperSizeUnits == PrintParameters::Inches)
{
pageSize = QPageSize(QSizeF {Params.paperSizeW, Params.paperSizeH}, QPageSize::Inch);
}
else
{
qDebug() << "Unsupported paper size" << Params.paperSizeName.c_str();
throw ConvertFailedException(tr("Unsupported paper size"));
}
QFile inFile(filename);
if(!inFile.open(QIODevice::ReadOnly))
{
throw ConvertFailedException(tr("Failed to open file"));
}
quint32 resolution = std::min(Params.hwResW, Params.hwResH);
QTemporaryFile tmpPdfFile;
tmpPdfFile.open();
QPdfWriter pdfWriter(tmpPdfFile.fileName());
pdfWriter.setCreator("SeaPrint " SEAPRINT_VERSION);
pdfWriter.setPageSize(pageSize);
pdfWriter.setResolution(resolution);
qreal docHeight = pageSize.sizePixels(resolution).height();
QTextDocument doc;
QFont font = QFont("Courier");
font.setPointSizeF(1);
qreal charHeight = 0;
// Find the optimal font size
while(true) {
QFont tmpFont = font;
tmpFont.setPointSizeF(font.pointSizeF()+0.5);
QFontMetricsF qfm(tmpFont, &pdfWriter);
charHeight = qfm.lineSpacing();
if(charHeight*66 > docHeight)
{
break;
}
font=tmpFont;
}
QFontMetricsF qfm(font, &pdfWriter);
charHeight = qfm.height();
int textHeight = 60*charHeight;
qreal margin = ((docHeight-textHeight)/2);
qreal mmMargin = margin/(resolution/25.4);
doc.setDefaultFont(font);
(void)doc.documentLayout(); // wat
// Needs to be before painter
pdfWriter.setMargins({mmMargin, mmMargin, mmMargin, mmMargin});
QPainter painter(&pdfWriter);
doc.documentLayout()->setPaintDevice(painter.device());
doc.setDocumentMargin(margin);
// Hack to make the document and pdfWriter margins overlap
// Apparently calls to painter.translate() stack... who knew!
painter.translate(-margin, -margin);
QRectF body = pageSize.rectPixels(resolution);
doc.setPageSize(body.size());
QString allText = inFile.readAll();
if(allText.startsWith("\f"))
{
allText.remove(0, 1);
}
if(allText.endsWith("\f"))
{
allText.chop(1);
}
else if(allText.endsWith("\f\n"))
{
allText.chop(2);
}
QStringList pages = allText.split('\f');
bool first = true;
int pageCount = 0;
for(QString page : pages)
{
if(!first)
{
pdfWriter.newPage();
}
first = false;
if(page.endsWith("\n"))
{
page.chop(1);
}
doc.setPlainText(page);
int p = 0; // Page number in this document, starting from 0
while(true)
{
painter.save();
painter.translate(body.left(), body.top() - p*body.height());
QRectF view(0, p*body.height(), body.width(), body.height());
painter.setClipRect(view);
QAbstractTextDocumentLayout::PaintContext context;
context.clip = view;
context.palette.setColor(QPalette::Text, Qt::black);
doc.documentLayout()->draw(&painter, context);
painter.restore();
p++;
pageCount++;
if(p >= doc.pageCount())
break;
pdfWriter.newPage();
}
}
painter.end();
convertPdf(tmpPdfFile.fileName(), header, Params);
qDebug() << "Finished";
qDebug() << "posted";
}
void PrinterWorker::convertPlaintextLabel(QString filename, Bytestream header, PrintParameters Params)
{
QFile inFile(filename);
if(!inFile.open(QIODevice::ReadOnly))
{
throw ConvertFailedException(tr("Failed to open file"));
}
QString allText = inFile.readAll();
while(allText.endsWith('\n'))
{
allText.chop(1);
}
if(allText.contains('\n') || allText.contains('\f'))
{
throw ConvertFailedException(tr("Multiline label not supported"));
}
QTemporaryFile tmpPdfFile;
tmpPdfFile.open();
// NB: running with rotated format - pdf2printable will sort that out
int pixelHeight = Params.getPaperSizeWInPixels();
QFont font = QFont("Arial");
font.setPixelSize(pixelHeight);
QFontMetrics fm(font);
QString bigText = "AXgjqÈÅÄÖþ";
QRect rect = fm.boundingRect(bigText);
while(rect.height() > pixelHeight*(8.0/10.0))
{
font.setPixelSize(font.pixelSize()-1);
fm = QFontMetrics(font);
rect = fm.boundingRect(bigText);
}
rect = fm.boundingRect(allText);
int pixelWidth = rect.width() + pixelHeight/2;
float ratio = pixelWidth*1.0/pixelHeight;
Params.paperSizeH = Params.paperSizeW * ratio;
QPdfWriter pdfWriter(tmpPdfFile.fileName());
pdfWriter.setCreator("SeaPrint " SEAPRINT_VERSION);
QPageSize pageSize({Params.getPaperSizeHInPoints(), Params.getPaperSizeWInPoints()}, QPageSize::Point);
pdfWriter.setPageSize(pageSize);
pdfWriter.setMargins({0,0,0,0});
pdfWriter.setResolution(Params.hwResH);
QPainter painter(&pdfWriter);
int xOffset = -rect.x() + pixelHeight/4;
int yOffset = -rect.y() + pixelHeight/10;
painter.setFont(font);
painter.drawText(xOffset, yOffset, allText);
painter.end();
convertPdf(tmpPdfFile.fileName(), header, Params);
qDebug() << "Finished";
qDebug() << "posted";
}
void PrinterWorker::awaitResult(CurlRequester& cr, QString callback)
{
Bytestream resMsg;
CURLcode res = cr.await(&resMsg);
QMetaObject::invokeMethod(_printer, callback.toStdString().c_str(), Qt::QueuedConnection,
Q_ARG(CURLcode, res),
Q_ARG(Bytestream, resMsg));
}

72
src/printerworker.h Normal file
View file

@ -0,0 +1,72 @@
#ifndef PRINTERWORKER_H
#define PRINTERWORKER_H
#include <QObject>
#include <memory>
#include "curlrequester.h"
#include "ippmsg.h"
#include "ppm2pwg/printparameters.h"
class IppPrinter;
class ConvertFailedException: public std::exception
{
private:
std::string _msg;
public:
ConvertFailedException(QString msg = "") : _msg(msg.toStdString())
{
}
virtual const char* what() const throw()
{
return _msg.c_str();
}
};
class PrinterWorker : public QObject
{
Q_OBJECT
public:
PrinterWorker(IppPrinter* parent);
~PrinterWorker();
private:
PrinterWorker();
public slots:
void urlChanged(QUrl url);
void getStrings(QUrl url);
void getImage(QUrl url);
void getPrinterAttributes(Bytestream msg);
void getJobs(Bytestream msg);
void cancelJob(Bytestream msg);
void identify(Bytestream msg);
void print(QString filename, QString mimeType, QString targetFormat, IppMsg job, PrintParameters Params, QMargins margins);
void print2(QString filename, QString mimeType, QString targetFormat, IppMsg createJob, IppMsg sendDocument, PrintParameters Params, QMargins margins);
private slots:
void cleanup();
signals:
void progress(qint64 done, qint64 pages);
void busyMessage(QString msg);
void failed(QString message);
private:
void justUpload(QString filename, Bytestream header);
void printImageAsImage(QString filename, Bytestream header, QString targetFormat);
void fixupPlaintext(QString filename, Bytestream header);
void convertPdf(QString filename, Bytestream header, PrintParameters Params);
void convertImage(QString filename, Bytestream header, PrintParameters Params, QMargins margins);
void convertOfficeDocument(QString filename, Bytestream header, PrintParameters Params);
void convertPlaintext(QString filename, Bytestream header, PrintParameters Params);
void convertPlaintextLabel(QString filename, Bytestream header, PrintParameters Params);
void awaitResult(CurlRequester& cr, QString callback);
std::unique_ptr<QThread> _thread;
QPointer<IppPrinter> _printer;
QUrl _url;
};
#endif // PRINTERWORKER_H

47
src/rangelistchecker.cpp Normal file
View file

@ -0,0 +1,47 @@
#include "rangelistchecker.h"
RangeListChecker::RangeListChecker()
{
}
RangeListChecker::~RangeListChecker()
{
}
RangeListChecker* RangeListChecker::m_Instance = 0;
RangeListChecker* RangeListChecker::instance()
{
static QMutex mutex;
if (!m_Instance)
{
mutex.lock();
if (!m_Instance)
m_Instance = new RangeListChecker;
mutex.unlock();
}
return m_Instance;
}
QJsonArray RangeListChecker::parse(QString str) const
{
PrintParameters params;
params.setPageRange(str.toStdString());
QJsonArray ret;
size_t previous = 0;
for(const std::pair<size_t, size_t>& p : params.pageRangeList)
{
if(p.first <= previous)
{
return {};
}
ret.append(QJsonObject {{"low", int(p.first)}, {"high", int(p.second)}});
previous = p.second;
}
return ret;
}

29
src/rangelistchecker.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef RANGELISTCHECKER_H
#define RANGELISTCHECKER_H
#include <QValidator>
#include <QMutex>
#include <QJsonArray>
#include <QJsonObject>
#include "printparameters.h"
class RangeListChecker : public QObject
{
Q_OBJECT
public:
static RangeListChecker* instance();
Q_INVOKABLE QJsonArray parse(QString str) const;
private:
static RangeListChecker* m_Instance;
RangeListChecker();
~RangeListChecker();
RangeListChecker(const RangeListChecker &);
RangeListChecker& operator=(const RangeListChecker &);
};
#endif // RANGELISTCHECKER_H

View file

@ -0,0 +1,21 @@
#include "seaprintdbusadaptor.h"
#include <QtQuick>
SeaPrintDBusAdaptor::SeaPrintDBusAdaptor(QQuickView *view)
: QDBusAbstractAdaptor(view)
, DBusAdaptorBase(view)
{
}
SeaPrintDBusAdaptor::~SeaPrintDBusAdaptor()
{
}
void SeaPrintDBusAdaptor::Open(const QString& fileName)
{
doOpen(fileName);
}
void SeaPrintDBusAdaptor::OpenPlaintext(const QString& fileContents, const QString& name)
{
doCreateFileAndOpen(fileContents, name);
}

21
src/seaprintdbusadaptor.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef SEAPRINTDBUSADAPTOR_H
#define SEAPRINTDBUSADAPTOR_H
#include "dbusadaptorbase.h"
class SeaPrintDBusAdaptor : public QDBusAbstractAdaptor, public DBusAdaptorBase
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "net.attah.seaprint")
public:
SeaPrintDBusAdaptor(QQuickView *view);
~SeaPrintDBusAdaptor();
public Q_SLOTS:
Q_NOREPLY void Open(const QString& fileName);
Q_NOREPLY void OpenPlaintext(const QString& fileContents, const QString& name);
};
#endif // SEAPRINTDBUSADAPTOR_H

View file

@ -2,7 +2,9 @@
#include <QMutex>
Settings::Settings() : _ignoreSslErrorsSetting("/apps/harbour-seaprint/settings/ignore-ssl-errors", this)
Settings::Settings() : _ignoreSslErrorsSetting("/apps/harbour-seaprint/settings/ignore-ssl-errors", this),
_debugLogSetting("/apps/harbour-seaprint/settings/debug-log", this),
_allowExternalConnectionsSetting("/apps/harbour-seaprint/settings/allow-external-connections", this)
{
}
@ -37,3 +39,23 @@ QString Settings::ignoreSslErrorsPath()
{
return _ignoreSslErrorsSetting.key();
}
bool Settings::debugLog()
{
return _debugLogSetting.value(_debugLogDefault).toBool();
}
QString Settings::debugLogPath()
{
return _debugLogSetting.key();
}
bool Settings::allowExternalConnections()
{
return _allowExternalConnectionsSetting.value(_allowExternalConnectionsDefault).toBool();
}
QString Settings::allowExternalConnectionsPath()
{
return _allowExternalConnectionsSetting.key();
}

View file

@ -11,10 +11,18 @@ public:
Q_PROPERTY(QString ignoreSslErrorsPath READ ignoreSslErrorsPath CONSTANT)
Q_PROPERTY(bool ignoreSslErrorsDefault MEMBER _ignoreSslErrorsDefault CONSTANT)
Q_PROPERTY(QString debugLogPath READ debugLogPath CONSTANT)
Q_PROPERTY(bool debugLogDefault MEMBER _debugLogDefault CONSTANT)
Q_PROPERTY(QString allowExternalConnectionsPath READ allowExternalConnectionsPath CONSTANT)
Q_PROPERTY(bool allowExternalConnectionsDefault MEMBER _allowExternalConnectionsDefault CONSTANT)
public:
static Settings* instance();
bool ignoreSslErrors();
bool debugLog();
bool allowExternalConnections();
private:
Settings();
@ -25,10 +33,17 @@ private:
static Settings* m_Instance;
MGConfItem _ignoreSslErrorsSetting;
MGConfItem _debugLogSetting;
MGConfItem _allowExternalConnectionsSetting;
QString ignoreSslErrorsPath();
QString debugLogPath();
QString allowExternalConnectionsPath();
bool _ignoreSslErrorsDefault = true;
bool _debugLogDefault = false;
bool _allowExternalConnectionsDefault = false;
};
#endif // SETTINGS_H

View file

@ -1,43 +0,0 @@
#include "svgprovider.h"
SvgProvider::SvgProvider() : QQuickImageProvider(QQuickImageProvider::Image, ForceAsynchronousImageLoading)
{
}
SvgProvider::~SvgProvider() {
}
SvgProvider* SvgProvider::m_Instance = nullptr;
SvgProvider* SvgProvider::instance()
{
static QMutex mutex;
if (!m_Instance)
{
mutex.lock();
if (!m_Instance)
m_Instance = new SvgProvider;
mutex.unlock();
}
return m_Instance;
}
QImage SvgProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
QSvgRenderer renderer(SailfishApp::pathTo(id).toLocalFile());
*size = requestedSize.isValid() ? requestedSize : renderer.defaultSize();
QImage img(*size, QImage::Format_ARGB32);
img.fill(QColor("transparent"));
QPainter painter(&img);
qDebug() << img << painter.isActive();
renderer.render(&painter);
return img;
}

View file

@ -1,28 +0,0 @@
#ifndef SVGPROVIDER_H
#define SVGPROVIDER_H
#include <QQuickImageProvider>
#include <QPainter>
#include <QtSvg/QSvgRenderer>
#include <QMutex>
#include <QtDebug>
#include <sailfishapp.h>
class SvgProvider : public QQuickImageProvider
{
public:
static SvgProvider* instance();
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
private:
SvgProvider();
~SvgProvider();
SvgProvider(const SvgProvider &);
SvgProvider& operator=(const SvgProvider &);
static SvgProvider* m_Instance;
};
#endif // SVGPROVIDER_H

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff