Compare commits

...

113 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
67 changed files with 37641 additions and 3877 deletions

View file

@ -11,7 +11,7 @@ A minority of printers support only PCL and/or their own proprietary formats, th
## Format support ## 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 * PDF
* Postscript * Postscript
* PWG-raster * 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 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 For printing "Office" files, like .doc(x) and odt, you need to install
`calligraconverter` (from the `calligra` package in Sailfish OS 4.2 onwards). `calligraconverter` (from the `calligra` package in Sailfish OS 4.2 onwards).
Then the printer support follows that of PDF. 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 * PDF
* Postscript * Postscript
For printing JPEG images, transferring the unaltered image has priority and the printer does the scaling. When printing images the result may depend on how they are transferred.
Other image formats are lossless and SeaPrint does scale-and-rotate to fit, so JPEG has lowest prio there. 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: 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 Icon=harbour-seaprint
X-Nemo-Application-Type=silica-qt5 X-Nemo-Application-Type=silica-qt5
Exec=harbour-seaprint Exec=harbour-seaprint
X-DBusActivatable=true
Name=SeaPrint Name=SeaPrint
[X-Sailjail] [X-Sailjail]
Permissions=Internet;Pictures;Documents;Downloads;MediaIndexing;Compatibility Permissions=Internet;Pictures;Documents;Downloads;RemovableMedia;MediaIndexing;Compatibility
OrganizationName=net.attah OrganizationName=net.attah
ApplicationName=seaprint ApplicationName=seaprint
ExecDBus=harbour-seaprint --prestart

View file

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

@ -1 +1 @@
Subproject commit a22548c112ffc9cc2f743b6b0c341ca053c0a52b Subproject commit acc32f3769c3b18bd6b94c55b882c7b7b778854c

View file

@ -7,33 +7,34 @@ Setting {
property var preferred_choices: [] property var preferred_choices: []
property string preferred_choice_suffix: "" 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 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: { 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", var dialog = pageStack.push("LargeChoiceDialog.qml",
{name:name, choice: choice != undefined ? choice : default_choice, choices: actual_choices, {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() { dialog.accepted.connect(function() {
choice = dialog.choice choice = dialog.choice
}) })
} }
else
{
menu.open(this)
}
} }
menu: ContextMenu { menu: ContextMenu {
Repeater { Repeater {
model: actual_choices.length>num_large_choices ? 0 : actual_choices model: actual_choices.length>num_large_choices ? 0 : actual_choices
MenuItem { 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 : "") + (Utils.has(preferred_choices, actual_choices[index]) ? " "+preferred_choice_suffix : "")
onClicked: 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

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

View file

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

View file

@ -12,6 +12,7 @@ Dialog {
property var choices property var choices
property var preferred_choices property var preferred_choices
property string preferred_choice_suffix property string preferred_choice_suffix
property var strings
canAccept: false canAccept: false
SilicaListView SilicaListView
@ -30,7 +31,7 @@ Dialog {
x: Theme.paddingLarge x: Theme.paddingLarge
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
highlighted: choices[index]==new_choice 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 : "") + (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 high
property int choice_low: 1 property int choice_low: 1
property int choice_high: 0 property int choice_high: 0
property bool acceptRangeList: false
property bool suppressChange: false property bool suppressChange: false
@ -41,9 +42,8 @@ Setting {
} }
onChoiceChanged: { onChoiceChanged: {
if(choice == undefined) if(choice == undefined || choice.constructor.name !== "Object")
{ {
console.log("choice unset");
suppressChange = true; suppressChange = true;
low_slider.value = low_slider.minimumValue; low_slider.value = low_slider.minimumValue;
high_slider.value = high_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 { menu: ContextMenu {
MenuItem { MenuItem {
@ -72,7 +104,7 @@ Setting {
anchors.right: parent.right anchors.right: parent.right
icon.source: "image://theme/icon-s-edit" icon.source: "image://theme/icon-s-edit"
onClicked: {var dialog = pageStack.push(Qt.resolvedUrl("IntegerInputDialog.qml"), onClicked: {var dialog = pageStack.push(Qt.resolvedUrl("IntegerInputDialog.qml"),
{value: choice, title: prettyName, {title: prettyName + " (" + qsTr("Low") + ")",
min: 1, max: high}); min: 1, max: high});
dialog.accepted.connect(function() { dialog.accepted.connect(function() {
choice_low = dialog.value; choice_low = dialog.value;
@ -100,7 +132,7 @@ Setting {
anchors.right: parent.right anchors.right: parent.right
icon.source: "image://theme/icon-s-edit" icon.source: "image://theme/icon-s-edit"
onClicked: {var dialog = pageStack.push(Qt.resolvedUrl("IntegerInputDialog.qml"), onClicked: {var dialog = pageStack.push(Qt.resolvedUrl("IntegerInputDialog.qml"),
{value: choice, title: prettyName, {title: prettyName + " (" + qsTr("High") + ")",
min: 1, max: high}); min: 1, max: high});
dialog.accepted.connect(function() { dialog.accepted.connect(function() {
choice_high = dialog.value; 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 choice
property var default_choice: parent.getDefaultChoice(name) property var default_choice: parent.getDefaultChoice(name)
property var strings: parent.printer.strings
Component.onCompleted: parent.setInitialChoice(this) Component.onCompleted: parent.setInitialChoice(this)
onChoiceChanged: parent.choiceMade(this) onChoiceChanged: parent.choiceMade(this)

View file

@ -84,6 +84,6 @@ Column {
delete jobParams[setting.subkey]; 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 var color: "white"
property int level: 100 property int level: 100
property int high_level: 100 property int high_level: 100
property int low_level: 0 property int low_level: 10
property string name: "" property string name: ""
property string type: "" property string type: ""
property real value_fraction: level/(high_level == 0 ? 100 : high_level)
spacing: Theme.paddingMedium spacing: Theme.paddingMedium
bottomPadding: Theme.paddingMedium bottomPadding: Theme.paddingMedium
@ -19,14 +20,14 @@ import "../components"
{ {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: parent.color color: parent.color
value: (level/high_level) value: value_fraction
} }
Label Label
{ {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: Theme.itemSizeExtraSmall width: Theme.itemSizeExtraSmall
text: ""+(100*level/high_level)+"%" text: ""+Math.round(100*value_fraction)+"%"
color: Theme.highlightColor color: level <= low_level ? "red" : Theme.highlightColor
font.pixelSize: Theme.fontSizeExtraSmall font.pixelSize: Theme.fontSizeExtraSmall
} }
Column Column

View file

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

View file

@ -13,9 +13,37 @@ ApplicationWindow
{ {
id: appWin id: appWin
property Page _mainPage
property bool expectCalligra: true 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") cover: Qt.resolvedUrl("cover/CoverPage.qml")
allowedOrientations: Orientation.All allowedOrientations: Orientation.All
@ -51,11 +79,9 @@ ApplicationWindow
db_conn.transaction(function (tx) { db_conn.transaction(function (tx) {
var res = tx.executeSql('SELECT * FROM Favourites WHERE ssid=?', [ssid]); var res = tx.executeSql('SELECT * FROM Favourites WHERE ssid=?', [ssid]);
for (var i = 0; i < res.rows.length; i++) { for (var i = 0; i < res.rows.length; i++) {
console.log(res.rows.item(i).url)
favs.push(res.rows.item(i).url); favs.push(res.rows.item(i).url);
} }
}); });
console.log(ssid, favs);
return favs return favs
} }
@ -77,10 +103,18 @@ ApplicationWindow
} }
function simplifyType(mimetype) { 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")}; return {simple: "image", translatable: qsTr("images")};
} }
else if(mimetype == Mimer.Plaintext)
{
return {simple: "plaintext", translatable: qsTr("plaintext")};
}
else else
{ {
return {simple: "document", translatable: qsTr("documents")}; return {simple: "document", translatable: qsTr("documents")};
@ -123,7 +157,6 @@ ApplicationWindow
expireTimeout: 4000 expireTimeout: 4000
function notify(data) { function notify(data) {
console.log("notifyMessage", data)
body = data body = data
previewBody = data previewBody = data
publish() publish()
@ -152,5 +185,19 @@ ApplicationWindow
defaultValue: SeaPrintSettings.ignoreSslErrorsDefault 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 { AboutLabel {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primaryColor 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 font.pixelSize: Theme.fontSizeSmall
text: qsTr("Chinese")+" - dashinfantry\n"+ text: qsTr("Chinese")+" - dashinfantry\n"+
qsTr("Dutch")+" - JSEHV, Rudi Timmermans\n"+ qsTr("Dutch")+" - JSEHV, Rudi Timmermans\n"+
qsTr("French")+" - ensag-dev, Quentí, phklrz\n"+ qsTr("French")+" - ensag-dev, pherjung, phklrz, Quentí\n"+
qsTr("German")+" - black-sheep-dev, wheelseal, cnlpete\n"+ qsTr("German")+" - black-sheep-dev, cnlpete, wheelseal\n"+
qsTr("Polish")+" - atlochowski\n"+ qsTr("Polish")+" - atlochowski, karolkurek\n"+
qsTr("Spanish")+" - carmenfdezb" qsTr("Spanish")+" - carmenfdezb, JSEHV"
} }
SectionHeader { text: qsTr("Optional dependencies") } SectionHeader { text: qsTr("Optional dependencies") }

View file

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

View file

@ -12,6 +12,18 @@ Page {
anchors.fill: parent anchors.fill: parent
contentHeight: col.height 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 { Column {
id: col id: col
@ -69,6 +81,27 @@ Page {
visible: printer.attrs.hasOwnProperty("printer-state-message") 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 { SectionHeader {
text: qsTr("IPP versions") text: qsTr("IPP versions")
visible: printer.attrs.hasOwnProperty("ipp-versions-supported") visible: printer.attrs.hasOwnProperty("ipp-versions-supported")
@ -115,25 +148,24 @@ Page {
} }
SectionHeader { SectionHeader {
text: qsTr("Supply status") text: qsTr("Performance")
visible: printer.attrs.hasOwnProperty("marker-colors") 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
{
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]
}
} }
Label
{
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.ippdiscovery 1.0
import seaprint.convertchecker 1.0 import seaprint.convertchecker 1.0
import seaprint.ippprinter 1.0 import seaprint.ippprinter 1.0
import seaprint.imageitem 1.0
import seaprint.mimer 1.0 import seaprint.mimer 1.0
import "utils.js" as Utils import "utils.js" as Utils
@ -11,16 +12,11 @@ Page {
id: page id: page
allowedOrientations: Orientation.All allowedOrientations: Orientation.All
property string selectedFile: ""
property string selectedFileType
Connections { Connections {
target: wifi target: wifi
onConnectedChanged: { onConnectedChanged: {
console.log("conn", wifi.connected, wifi.ssid)
if(wifi.connected) { if(wifi.connected) {
var favourites = db.getFavourites(wifi.ssid); var favourites = db.getFavourites(wifi.ssid);
console.log(favourites);
IppDiscovery.favourites = favourites; IppDiscovery.favourites = favourites;
} }
else { else {
@ -31,7 +27,6 @@ Page {
property bool initialSSIDchange: true property bool initialSSIDchange: true
onSsidChanged: { onSsidChanged: {
console.log("ssid changed", wifi.ssid);
if(!initialSSIDchange) if(!initialSSIDchange)
{ {
IppDiscovery.reset(); IppDiscovery.reset();
@ -61,14 +56,7 @@ Page {
signal noFileSelected() signal noFileSelected()
Component.onCompleted: { Component.onCompleted: {
console.log("Selected file is", selectedFile)
IppDiscovery.discover(); IppDiscovery.discover();
if(selectedFile != "")
{
var type = Mimer.get_type(selectedFile);
console.log(type);
selectedFileType = type;
}
} }
property bool nagged: false property bool nagged: false
@ -76,7 +64,6 @@ Page {
onStatusChanged: { onStatusChanged: {
if(status==PageStatus.Active && !nagged && nagScreenSetting.value != nagScreenSetting.expectedValue) if(status==PageStatus.Active && !nagged && nagScreenSetting.value != nagScreenSetting.expectedValue)
{ {
console.log("Can convert from Office:", ConvertChecker.calligra)
if(expectCalligra && !ConvertChecker.calligra) if(expectCalligra && !ConvertChecker.calligra)
{ {
nagged=true nagged=true
@ -108,7 +95,6 @@ Page {
onClicked: { onClicked: {
var dialog = pageStack.push(Qt.resolvedUrl("AddPrinterDialog.qml")); var dialog = pageStack.push(Qt.resolvedUrl("AddPrinterDialog.qml"));
dialog.accepted.connect(function() { dialog.accepted.connect(function() {
console.log("add", wifi.ssid, dialog.value);
db.addFavourite(wifi.ssid, dialog.value); db.addFavourite(wifi.ssid, dialog.value);
IppDiscovery.favourites = db.getFavourites(dialog.ssid); IppDiscovery.favourites = db.getFavourites(dialog.ssid);
}) })
@ -149,7 +135,7 @@ Page {
property string name: Utils.unknownForEmptyString(printer.attrs["printer-name"].value) property string name: Utils.unknownForEmptyString(printer.attrs["printer-name"].value)
property var supported_formats: Utils.supported_formats(printer, considerAdditionalFormatsSetting.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 { Connections {
target: printer target: printer
@ -167,7 +153,6 @@ Page {
Connections { Connections {
target: page target: page
onRefreshed: { onRefreshed: {
console.log("onRefreshed")
printer.refresh() printer.refresh()
} }
} }
@ -189,7 +174,7 @@ Page {
{ {
if(printer.attrs.hasOwnProperty("printer-uuid")) 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 else
{ {
@ -206,7 +191,7 @@ Page {
return; return;
} }
debugCountReset.restart(); debugCountReset.restart();
if(selectedFile == "") if(appWin.selectedFile == "")
{ {
noFileSelected(); noFileSelected();
} }
@ -216,11 +201,11 @@ Page {
} }
else 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 id: icon
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
@ -229,13 +214,17 @@ Page {
height: Theme.itemSizeLarge height: Theme.itemSizeLarge
width: Theme.itemSizeLarge width: Theme.itemSizeLarge
sourceSize.height: height image: printer.icon
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"
Image {
id: placeholder
anchors.fill: parent
sourceSize.height: height
sourceSize.width: width
visible: !parent.valid
source: "icon-seaprint-nobg.svg"
}
} }
Image { Image {
@ -253,13 +242,13 @@ Page {
Label { Label {
id: name_label id: name_label
color: canPrint || selectedFile == "" ? Theme.primaryColor : Theme.secondaryColor color: canPrint || appWin.selectedFile == "" ? Theme.primaryColor : Theme.secondaryColor
text: name text: name
} }
Label { Label {
id: mm_label id: mm_label
color: canPrint || selectedFile == "" ? Theme.primaryColor : Theme.secondaryColor color: canPrint || appWin.selectedFile == "" ? Theme.primaryColor : Theme.secondaryColor
font.pixelSize: Theme.fontSizeExtraSmall font.pixelSize: Theme.fontSizeExtraSmall
text: Utils.unknownForEmptyString(printer.attrs["printer-make-and-model"].value) text: Utils.unknownForEmptyString(printer.attrs["printer-make-and-model"].value)
+ (Utils.existsAndNotEmpty("printer-location", printer) ? " • "+printer.attrs["printer-location"].value : "") + (Utils.existsAndNotEmpty("printer-location", printer) ? " • "+printer.attrs["printer-location"].value : "")
@ -267,7 +256,7 @@ Page {
Label { Label {
id: uri_label id: uri_label
color: canPrint || selectedFile == "" ? Theme.highlightColor : Theme.secondaryColor color: canPrint || appWin.selectedFile == "" ? Theme.highlightColor : Theme.secondaryColor
font.pixelSize: Theme.fontSizeTiny font.pixelSize: Theme.fontSizeTiny
text: printer.url text: printer.url
} }
@ -288,7 +277,7 @@ Page {
width: Theme.itemSizeExtraSmall/2 width: Theme.itemSizeExtraSmall/2
visible: supported_formats.pdf visible: supported_formats.pdf
highlightColor: "red" highlightColor: "red"
highlighted: !(selectedFile == "" || canPrint) highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-pdf" source: "image://theme/icon-m-file-pdf"
} }
HighlightImage { HighlightImage {
@ -296,7 +285,7 @@ Page {
width: Theme.itemSizeExtraSmall/2 width: Theme.itemSizeExtraSmall/2
visible: supported_formats.postscript visible: supported_formats.postscript
highlightColor: "red" highlightColor: "red"
highlighted: !(selectedFile == "" || canPrint) highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-other" source: "image://theme/icon-m-file-other"
} }
@ -305,7 +294,7 @@ Page {
width: Theme.itemSizeExtraSmall/2 width: Theme.itemSizeExtraSmall/2
visible: supported_formats.plaintext visible: supported_formats.plaintext
highlightColor: "red" highlightColor: "red"
highlighted: !(selectedFile == "" || canPrint) highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-document" source: "image://theme/icon-m-file-document"
} }
HighlightImage { HighlightImage {
@ -313,7 +302,7 @@ Page {
width: Theme.itemSizeExtraSmall/2 width: Theme.itemSizeExtraSmall/2
visible: supported_formats.office visible: supported_formats.office
highlightColor: "red" highlightColor: "red"
highlighted: !(selectedFile == "" || canPrint) highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-formatted" source: "image://theme/icon-m-file-formatted"
} }
HighlightImage { HighlightImage {
@ -321,7 +310,7 @@ Page {
width: Theme.itemSizeExtraSmall/2 width: Theme.itemSizeExtraSmall/2
visible: supported_formats.office visible: supported_formats.office
highlightColor: "red" highlightColor: "red"
highlighted: !(selectedFile == "" || canPrint) highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-presentation" source: "image://theme/icon-m-file-presentation"
} }
HighlightImage { HighlightImage {
@ -329,7 +318,7 @@ Page {
width: Theme.itemSizeExtraSmall/2 width: Theme.itemSizeExtraSmall/2
visible: supported_formats.images visible: supported_formats.images
highlightColor: "red" highlightColor: "red"
highlighted: !(selectedFile == "" || canPrint) highlighted: !(appWin.selectedFile == "" || canPrint)
source: "image://theme/icon-m-file-image" source: "image://theme/icon-m-file-image"
} }
} }
@ -363,12 +352,16 @@ Page {
} }
} }
} }
DockedPanel { Rectangle {
id: fileDock id: fileDock
open: true
height: panelColumn.implicitHeight height: panelColumn.implicitHeight
width: parent.width 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 { Column {
id: panelColumn id: panelColumn
@ -380,7 +373,7 @@ Page {
Row { Row {
id: warningRow id: warningRow
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: Mimer.isOffice(page.selectedFileType) visible: Mimer.isOffice(appWin.selectedFileType)
HighlightImage { HighlightImage {
source: "image://theme/icon-s-warning" source: "image://theme/icon-s-warning"
@ -403,7 +396,7 @@ Page {
horizontalAlignment: contentWidth > width ? Text.AlignRight : Text.AlignHCenter horizontalAlignment: contentWidth > width ? Text.AlignRight : Text.AlignHCenter
truncationMode: TruncationMode.Fade truncationMode: TruncationMode.Fade
text: selectedFile != "" ? selectedFile : qsTr("No file selected") text: appWin.selectedFile != "" ? appWin.selectedFile : qsTr("No file selected")
SequentialAnimation { SequentialAnimation {
id: noFileSelectedAnimation id: noFileSelectedAnimation
@ -416,7 +409,6 @@ Page {
Connections { Connections {
target: page target: page
onNoFileSelected: { onNoFileSelected: {
console.log("onNoFileSelected")
noFileSelectedAnimation.start() noFileSelectedAnimation.start()
} }
} }
@ -457,19 +449,7 @@ Page {
title: qsTr("Choose file") title: qsTr("Choose file")
onSelectedContentPropertiesChanged: { onSelectedContentPropertiesChanged: {
var mimeType = Mimer.get_type(selectedContentProperties.filePath) appWin.openFile(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 = ""
}
} }
} }
} }
@ -479,8 +459,7 @@ Page {
allowedOrientations: Orientation.All allowedOrientations: Orientation.All
onSelectedContentPropertiesChanged: { onSelectedContentPropertiesChanged: {
page.selectedFile = selectedContentProperties.filePath appWin.openFile(selectedContentProperties.filePath);
page.selectedFileType = Mimer.get_type(selectedContentProperties.filePath)
} }
} }
} }

View file

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

View file

@ -65,7 +65,7 @@ Page {
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: qsTr("Install calligra:") + " " + (ConvertChecker.calligra ? qsTr("(done)") : "") text: qsTr("Install Calligra:") + " " + (ConvertChecker.calligra ? qsTr("(done)") : "")
} }
Label { Label {
@ -78,6 +78,16 @@ Page {
Clipper {} 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{ Item{
width: parent.width width: parent.width
height: Theme.itemSizeMedium height: Theme.itemSizeMedium

View file

@ -61,7 +61,7 @@ Page {
onClicked: { onClicked: {
var tmp = jobParams; var tmp = jobParams;
// Varies between documents, would be confusing to save // 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)) db.setJobSettings(printer.attrs["printer-uuid"].value, selectedFileType, JSON.stringify(tmp))
} }
} }
@ -69,7 +69,6 @@ Page {
MenuItem { MenuItem {
text: qsTr("Print") text: qsTr("Print")
onClicked: { onClicked: {
console.log(JSON.stringify(jobParams))
pageStack.replace(Qt.resolvedUrl("BusyPage.qml"),{printer:printer}, pageStack.replace(Qt.resolvedUrl("BusyPage.qml"),{printer:printer},
PageStackAction.Immediate) PageStackAction.Immediate)
printer.print(jobParams, page.selectedFile) printer.print(jobParams, page.selectedFile)
@ -105,6 +104,8 @@ Page {
tag: IppMsg.Integer tag: IppMsg.Integer
name: "copies" name: "copies"
prettyName: qsTr("Copies") prettyName: qsTr("Copies")
valid: _valid || Utils.supports_raster(printer)
minimum_high: 99
} }
ChoiceSetting { ChoiceSetting {
tag: IppMsg.Keyword tag: IppMsg.Keyword
@ -119,6 +120,7 @@ Page {
property var pdfpages: ConvertChecker.pdfPages(selectedFile) property var pdfpages: ConvertChecker.pdfPages(selectedFile)
high: pdfpages == 0 ? 65535 : pdfpages high: pdfpages == 0 ? 65535 : pdfpages
acceptRangeList: true
} }
ChoiceSetting { ChoiceSetting {
tag: IppMsg.Integer tag: IppMsg.Integer
@ -149,10 +151,10 @@ Page {
tag: IppMsg.Keyword tag: IppMsg.Keyword
name: "print-scaling" name: "print-scaling"
prettyName: qsTr("Scaling") prettyName: qsTr("Scaling")
valid: _valid && selectedFileType == Mimer.JPEG valid: _valid && Mimer.isImage(selectedFileType) && selectedFileType != Mimer.SVG
DependentOn { DependentOn {
target: transferFormatSetting target: transferFormatSetting
values: [Mimer.JPEG] values: [Mimer.JPEG, Mimer.PNG]
} }
} }
ChoiceSetting { ChoiceSetting {
@ -197,6 +199,7 @@ Page {
name: "media-top-margin" name: "media-top-margin"
subkey: "media-col" subkey: "media-col"
prettyName: qsTr("Top") prettyName: qsTr("Top")
valid: _valid && Mimer.isImage(selectedFileType)
} }
ChoiceSetting { ChoiceSetting {
visible: marginsButton.active visible: marginsButton.active
@ -204,6 +207,7 @@ Page {
name: "media-bottom-margin" name: "media-bottom-margin"
subkey: "media-col" subkey: "media-col"
prettyName: qsTr("Bottom") prettyName: qsTr("Bottom")
valid: _valid && Mimer.isImage(selectedFileType)
} }
ChoiceSetting { ChoiceSetting {
visible: marginsButton.active visible: marginsButton.active
@ -211,6 +215,7 @@ Page {
name: "media-left-margin" name: "media-left-margin"
subkey: "media-col" subkey: "media-col"
prettyName: qsTr("Left") prettyName: qsTr("Left")
valid: _valid && Mimer.isImage(selectedFileType)
} }
ChoiceSetting { ChoiceSetting {
visible: marginsButton.active visible: marginsButton.active
@ -218,6 +223,7 @@ Page {
name: "media-right-margin" name: "media-right-margin"
subkey: "media-col" subkey: "media-col"
prettyName: qsTr("Right") 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 Version: 0
Release: 1 Release: 1
Group: Qt/Qt Group: Qt/Qt
License: LICENSE License: GPLv3
URL: http://example.org/ URL: https://github.com/attah/harbour-seaprint
Source0: %{name}-%{version}.tar.bz2 Source0: %{name}-%{version}.tar.bz2
Requires: sailfishsilica-qt5 >= 0.10.9 Requires: sailfishsilica-qt5 >= 0.10.9
BuildRequires: pkgconfig(sailfishapp) >= 1.0.2 BuildRequires: pkgconfig(sailfishapp) >= 1.0.2
BuildRequires: pkgconfig(Qt5Core) BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5Qml) BuildRequires: pkgconfig(Qt5Qml)
BuildRequires: pkgconfig(Qt5Quick) BuildRequires: pkgconfig(Qt5Quick)
BuildRequires: pkgconfig(Qt5Svg)
BuildRequires: pkgconfig(mlite5) BuildRequires: pkgconfig(mlite5)
BuildRequires: desktop-file-utils BuildRequires: desktop-file-utils
BuildRequires: pkgconfig(libcurl) BuildRequires: pkgconfig(libcurl)
@ -20,6 +19,10 @@ BuildRequires: pkgconfig(poppler)
BuildRequires: pkgconfig(poppler-glib) BuildRequires: pkgconfig(poppler-glib)
BuildRequires: pkgconfig(glib-2.0) BuildRequires: pkgconfig(glib-2.0)
BuildRequires: pkgconfig(cairo) BuildRequires: pkgconfig(cairo)
BuildRequires: pkgconfig(libjpeg)
%define _source_payload w6T.xzdio
%define _binary_payload w6T.xzdio
%description %description
Network printing for Sailfish OS Network printing for Sailfish OS
@ -44,7 +47,7 @@ desktop-file-install --delete-original \
%files %files
%defattr(-,root,root,-) %defattr(-,root,root,-)
%{_bindir} %{_bindir}/%{name}
%{_datadir}/%{name} %{_datadir}/%{name}
%{_datadir}/applications/%{name}.desktop %{_datadir}/applications/%{name}.desktop
%{_datadir}/icons/hicolor/*/apps/%{name}.png %{_datadir}/icons/hicolor/*/apps/%{name}.png

View file

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

View file

@ -1,28 +1,104 @@
#include "curlrequester.h" #include "curlrequester.h"
#include <algorithm> #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() CurlRequester::~CurlRequester()
{ {
while(!_canWrite.tryAcquire(1, 500)) await();
{
if(!_performer.isRunning())
{
break;
}
}
_done = true;
_canRead.release();
_performer.wait();
if(_dest != nullptr) if(_dest != nullptr)
{ {
delete _dest; 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) 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; qDebug() << "write " << size;
while(!_canWrite.tryAcquire(1, 500)) while(!_canWrite.tryAcquire(1, 500))
{ {
if(!_performer.isRunning()) if(!_worker.isRunning())
{ {
return false; return false;
} }
@ -53,7 +129,7 @@ size_t CurlRequester::requestWrite(char* dest, size_t size)
if(!_reading) if(!_reading)
{ {
_canRead.acquire(); _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; return 0;
} }

View file

@ -4,30 +4,55 @@
#include <QUrl> #include <QUrl>
#include <QThread> #include <QThread>
#include <QSemaphore> #include <QSemaphore>
#include <QMetaMethod> #include <QtConcurrent/QtConcurrent>
#include <curl/curl.h> #include <curl/curl.h>
#include <bytestream.h>
#include <QDebug> #include <QDebug>
#include "curlworker.h" #include <functional>
class CurlRequester : public QObject class CurlRequester : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
CurlRequester(QUrl addr); enum Role {
IppRequest,
HttpGetRequest
};
CurlRequester(QUrl addr, Role role = IppRequest, Bytestream* = nullptr);
~CurlRequester(); ~CurlRequester();
template<typename Class, typename Callback> CURLcode await(Bytestream* = nullptr);
bool setFinishedCallback(const Class* receiverObject, Callback cb)
{
connect(&_performer, &CurlWorker::done, receiverObject, cb);
return true;
}
bool write(const char *data, size_t size); bool write(const char *data, size_t size);
size_t requestWrite(char* dest, 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: 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; QUrl _addr;
QSemaphore _canWrite; QSemaphore _canWrite;
@ -35,11 +60,19 @@ private:
bool _reading; bool _reading;
bool _done; bool _done;
CURLcode _result;
Bytestream _resultMsg;
char* _dest; char* _dest;
size_t _size; size_t _size;
size_t _offset; size_t _offset;
CurlWorker _performer; friend class CurlWorker;
CURL* _curl;
struct curl_slist* _opts = NULL;
QFuture<void> _worker;
}; };
#endif // CURLREQUESTER_H #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 <QtQuick>
#include <QVariant>
#include <QDBusConnection>
#include <sailfishapp.h> #include <sailfishapp.h>
#include <src/ippdiscovery.h> #include <src/ippdiscovery.h>
#include <src/ippprinter.h> #include <src/ippprinter.h>
#include <src/imageitem.h>
#include <src/mimer.h> #include <src/mimer.h>
#include <src/convertchecker.h> #include <src/convertchecker.h>
#include <src/svgprovider.h>
#include <src/settings.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(CURLcode)
Q_DECLARE_METATYPE(Bytestream) Q_DECLARE_METATYPE(Bytestream)
Q_DECLARE_METATYPE(PrintParameters)
template <class T> template <class T>
static QObject* singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine) 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[]) 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<CURLcode>();
qRegisterMetaType<Bytestream>(); 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); 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<IppDiscovery>("seaprint.ippdiscovery", 1, 0, "IppDiscovery", singletontype_provider<IppDiscovery>);
qmlRegisterSingletonType<Mimer>("seaprint.mimer", 1, 0, "Mimer", singletontype_provider<Mimer>); qmlRegisterSingletonType<Mimer>("seaprint.mimer", 1, 0, "Mimer", singletontype_provider<Mimer>);
qmlRegisterSingletonType<ConvertChecker>("seaprint.convertchecker", 1, 0, "ConvertChecker", singletontype_provider<ConvertChecker>); 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<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"); qmlRegisterUncreatableType<IppMsg>("seaprint.ippmsg", 1, 0, "IppMsg", "Only used to supply an enum type");
QQuickView* view = SailfishApp::createView(); QQuickView* view = SailfishApp::createView();
view->engine()->addImportPath(SailfishApp::pathTo("qml/pages").toString()); 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()); view->setSource(SailfishApp::pathToMainQml());
view->show();
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(); 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; return addr;
} }
IppDiscovery::IppDiscovery() : QStringListModel(), QQuickImageProvider(QQuickImageProvider::Image, ForceAsynchronousImageLoading) IppDiscovery::IppDiscovery() : QStringListModel()
{ {
socket = new QUdpSocket(this); socket = new QUdpSocket(this);
connect(socket, &QUdpSocket::readyRead, this, &IppDiscovery::readPendingDatagrams); connect(socket, &QUdpSocket::readyRead, this, &IppDiscovery::readPendingDatagrams);
@ -93,15 +93,15 @@ void IppDiscovery::sendQuery(quint16 qtype, QStringList addrs) {
QTime now = QTime::currentTime(); QTime now = QTime::currentTime();
QTime aWhileAgo = now.addSecs(-1); QTime aWhileAgo = now.addSecs(-1);
foreach(QString oq, _outstandingQueries.keys()) for(QPair<quint16, QString> oq : _outstandingQueries.keys())
{ {
if(_outstandingQueries[oq] < aWhileAgo) if(_outstandingQueries[oq] < aWhileAgo)
{ // Housekeeping for _outstandingQueries { // Housekeeping for _outstandingQueries
_outstandingQueries.remove(oq); _outstandingQueries.remove(oq);
} }
else if(addrs.contains(oq)) else if(oq.first == qtype && addrs.contains(oq.second))
{ // we recently asked about this, remove it { // 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; 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("."); QStringList addrParts = addr.split(".");
QString addrPart, restAddr; QString addrPart, restAddr;
@ -153,9 +153,7 @@ void IppDiscovery::sendQuery(quint16 qtype, QStringList addrs) {
} }
QByteArray bytes((char*)(query.raw()), query.size()); socket->writeDatagram((char*)(query.raw()), query.size(), QHostAddress("224.0.0.251"), 5353);
socket->writeDatagram(bytes, QHostAddress("224.0.0.251"), 5353);
} }
QString make_addr(QString proto, int defaultPort, quint16 port, QString ip, QString rp) 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; QList<QPair<QString,QString>> ippsIpRps;
QString target, rp; QString target, rp;
foreach(QString it, _ipps) for(QString it : _ipps)
{ {
quint16 port = _ports[it]; if(!_targets.contains(it) || !_ports.contains(it) || !_rps.contains(it))
target = _targets[it]; continue;
rp = _rps[it];
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++) 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]; if(!_targets.contains(it) || !_ports.contains(it) || !_rps.contains(it))
target = _targets[it]; continue;
rp = _rps[it];
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++) 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 // 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 // and it can consider _favourites, so we can drop cleanUpdate
foreach(QString f, found) for(QString f : found)
{ {
if(!this->stringList().contains(f)) if(!this->stringList().contains(f))
{ {
@ -252,16 +257,23 @@ void IppDiscovery::update()
void IppDiscovery::updateAndQueryPtrs(QStringList& ptrs, QStringList new_ptrs) void IppDiscovery::updateAndQueryPtrs(QStringList& ptrs, QStringList new_ptrs)
{ {
new_ptrs.removeDuplicates(); new_ptrs.removeDuplicates();
foreach(QString ptr, new_ptrs) for(QString ptr : new_ptrs)
{ {
if(!ptrs.contains(ptr)) if(!ptrs.contains(ptr))
{ {
ptrs.append(ptr); ptrs.append(ptr);
} }
// If pointer does not resolve to a target or is missing information, query about it // 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(!_rps.contains(ptr))
{ // if the PTR doesn't already resolve, ask for everything about it {
sendQuery(ALL, {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; std::string tmprp;
tmp/tmp.remaining() >> tmprp; tmp/tmp.remaining() >> tmprp;
qDebug() << "tmprp" << aaddr << tmprp.c_str();
_rps[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 ipp ptrs" << new_ipp_ptrs;
qDebug() << "new ipps ptrs" << new_ipps_ptrs; qDebug() << "new ipps ptrs" << new_ipps_ptrs;
qDebug() << "ipp ptrs" << _ipp; qDebug() << "ipp ptrs" << _ipp;
qDebug() << "ipp ptrs" << _ipps; qDebug() << "ipps ptrs" << _ipps;
qDebug() << "rps" << _rps; qDebug() << "rps" << _rps;
qDebug() << "ports" << _ports; qDebug() << "ports" << _ports;
qDebug() << "new targets" << new_targets; qDebug() << "new targets" << new_targets;
@ -380,12 +393,12 @@ void IppDiscovery::readPendingDatagrams()
// These will send one query per unique new ptr. // These will send one query per unique new ptr.
// some responders doesn't give TXT records for more than one thing at at time :( // 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(_ipps, new_ipps_ptrs);
updateAndQueryPtrs(_ipp, new_ipp_ptrs);
QStringList unresolvedAddrs; QStringList unresolvedAddrs;
foreach(QString t, new_targets) for(QString t : new_targets)
{ {
// If target does not resolve to an address, query about it // If target does not resolve to an address, query about it
if(!_AAs.contains(t)) if(!_AAs.contains(t))
@ -404,15 +417,8 @@ void IppDiscovery::readPendingDatagrams()
} }
QImage IppDiscovery::requestImage(const QString &id, QSize *size, const QSize &requestedSize) bool IppDiscovery::resolve(QUrl& url)
{ //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);
QString host = url.host(); QString host = url.host();
if(host.endsWith(".")) if(host.endsWith("."))
@ -420,31 +426,11 @@ QImage IppDiscovery::requestImage(const QString &id, QSize *size, const QSize &r
host.chop(1); host.chop(1);
} }
qDebug() << url.host() << host << _AAs;
// TODO IPv6 // TODO IPv6
if(_AAs.contains(host)) if(_AAs.contains(host))
{ // TODO: retry potential other IPs { // TODO: retry potential other IPs
url.setHost(_AAs.value(host)); url.setHost(_AAs.value(host));
return true;
} }
return false;
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;
} }

View file

@ -1,16 +1,11 @@
#ifndef IPPDISCOVERY_H #ifndef IPPDISCOVERY_H
#define IPPDISCOVERY_H #define IPPDISCOVERY_H
#include <QStringListModel> #include <QStringListModel>
#include <QQuickImageProvider>
#include <QUdpSocket> #include <QUdpSocket>
#include <QMutex> #include <QMutex>
#include <QImageReader>
#include <QtNetwork>
#include <QNetworkAccessManager>
#include <QEventLoop>
#include "bytestream.h" #include "bytestream.h"
class IppDiscovery : public QStringListModel, public QQuickImageProvider class IppDiscovery : public QStringListModel
{ {
Q_OBJECT Q_OBJECT
public: public:
@ -21,6 +16,8 @@ public:
Q_INVOKABLE void discover(); Q_INVOKABLE void discover();
Q_INVOKABLE void reset(); Q_INVOKABLE void reset();
bool resolve(QUrl& url);
signals: signals:
void favouritesChanged(); void favouritesChanged();
void ignoreSslErrorsChanged(); void ignoreSslErrorsChanged();
@ -47,9 +44,6 @@ private:
void updateAndQueryPtrs(QStringList& ptrs, QStringList new_ptrs); void updateAndQueryPtrs(QStringList& ptrs, QStringList new_ptrs);
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
QStringList _ipp; QStringList _ipp;
QStringList _ipps; QStringList _ipps;
QMap<QString,QString> _rps; QMap<QString,QString> _rps;
@ -59,7 +53,7 @@ private:
QMultiMap<QString,QString> _AAs; QMultiMap<QString,QString> _AAs;
QMultiMap<QString,QString> _AAAAs; QMultiMap<QString,QString> _AAAAs;
QMap<QString, QTime> _outstandingQueries; QMap<QPair<quint16, QString>, QTime> _outstandingQueries;
QStringList _favourites; QStringList _favourites;
QUdpSocket* socket; 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; _majVsn = majVsn;
_minVsn = minVsn; _minVsn = minVsn;
_opAttrs = opAttrs; _opAttrs = opAttrs;
@ -76,40 +77,63 @@ QJsonValue IppMsg::consume_value(quint8 tag, Bytestream& data)
Q_ASSERT(false); Q_ASSERT(false);
case Integer: case Integer:
case Enum: 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; value = (int)tmp_u32;
break; break;
}
case Boolean: 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; value = (bool)tmp_bool;
break; break;
}
case DateTime: case DateTime:
{ {
quint16 year; QDateTime tmp_datetime;
quint8 month, day, hour, minutes, seconds, deci_seconds, if(!(data >>= (quint16)0))
plus_minus, utc_h_offset, utc_m_offset; {
data >> tmp_len >> year >> month >> day >> hour >> minutes >> seconds >> deci_seconds quint16 year;
>> plus_minus >> utc_h_offset >> utc_m_offset; quint8 month, day, hour, minutes, seconds, deci_seconds,
QDate date(year, month, day); plus_minus, utc_h_offset, utc_m_offset;
QTime time(hour, minutes, seconds, deci_seconds*100); data >> (quint16)11 >> year >> month >> day >> hour >> minutes >> seconds >> deci_seconds
int offset_seconds = (plus_minus == '+' ? 1 : -1)*(utc_h_offset*60*60+utc_m_offset*60); >> plus_minus >> utc_h_offset >> utc_m_offset;
value = QDateTime(date, time, Qt::OffsetFromUTC, offset_seconds).toString(Qt::ISODate); 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);
tmp_datetime = QDateTime(date, time, Qt::OffsetFromUTC, offset_seconds);
}
value = tmp_datetime.toString(Qt::ISODate);
break; break;
} }
case Resolution: case Resolution:
{ {
qint32 x, y; qint32 x=0;
qint8 units; qint32 y=0;
data >> tmp_len >> x >> y >> units; qint8 units=0;
if(!(data >>= (quint16)0))
{
data >> (quint16)9 >> x >> y >> units;
}
value = QJsonObject {{"x", x}, {"y", y}, {"units", units}}; value = QJsonObject {{"x", x}, {"y", y}, {"units", units}};
break; break;
} }
case IntegerRange: case IntegerRange:
{ {
qint32 low, high; qint32 low=0;
data >> tmp_len >> low >> high; qint32 high=0;
if(!(data >>= (quint16)0))
{
data >> (quint16)8 >> low >> high;
}
value = QJsonObject {{"low", low}, {"high", high}}; value = QJsonObject {{"low", low}, {"high", high}};
break; break;
} }
@ -159,7 +183,7 @@ QJsonArray IppMsg::get_unnamed_attributes(Bytestream& data)
QJsonArray untag_values(QJsonArray taggedValues) QJsonArray untag_values(QJsonArray taggedValues)
{ {
QJsonArray res; QJsonArray res;
foreach(QJsonValue it, taggedValues) for(QJsonValue it : taggedValues)
{ {
res.append(it.toObject()["value"]); res.append(it.toObject()["value"]);
} }
@ -251,17 +275,11 @@ QString IppMsg::consume_attribute(QJsonObject& attrs, Bytestream& data)
name = tmp_str.c_str(); name = tmp_str.c_str();
taggedValue = consume_value(tag, data); taggedValue = consume_value(tag, data);
QJsonArray unnamed = get_unnamed_attributes(data); QJsonArray unnamed = get_unnamed_attributes(data);
// qDebug() << name0 << tag << tmp_len << value << unnamed;
if(tag == BeginCollection) if(tag == BeginCollection)
{ {
// qDebug() << "Unnamed attrs for collection" << unnamed;
QJsonValue collected = collect_attributes(unnamed); QJsonValue collected = collect_attributes(unnamed);
// qDebug() << "collected" << collected;
taggedValue = QJsonObject {{"tag", tag}, {"value", collected}}; taggedValue = QJsonObject {{"tag", tag}, {"value", collected}};
} }
@ -270,6 +288,7 @@ QString IppMsg::consume_attribute(QJsonObject& attrs, Bytestream& data)
|| name.startsWith("printer-firmware") || name.startsWith("printer-firmware")
|| name.endsWith("-supported") || name.endsWith("-supported")
|| name == "printer-icons" || name == "printer-icons"
|| name == "media-ready"
|| name.endsWith("-reasons"))); || name.endsWith("-reasons")));
if(!unnamed.empty() || forceArray) if(!unnamed.empty() || forceArray)
@ -285,13 +304,13 @@ QString IppMsg::consume_attribute(QJsonObject& attrs, Bytestream& data)
return name; return name;
} }
Bytestream IppMsg::encode(Operation op) Bytestream IppMsg::encode()
{ {
Bytestream ipp; Bytestream ipp;
ipp << _majVsn << _minVsn; ipp << _majVsn << _minVsn;
ipp << quint16(op); ipp << quint16(_operation);
ipp << _reqid++; ipp << _reqid++;
@ -301,11 +320,15 @@ Bytestream IppMsg::encode(Operation op)
QStringList InitialAttrs = {"attributes-charset", QStringList InitialAttrs = {"attributes-charset",
"attributes-natural-language", "attributes-natural-language",
"printer-uri", "printer-uri",
"job-id",
"requesting-user-name"}; "requesting-user-name"};
foreach(QString key, InitialAttrs) for(QString key : InitialAttrs)
{ {
QJsonObject val = _opAttrs.take(key).toObject(); if(_opAttrs.find(key) != _opAttrs.end())
encode_attr(ipp, val["tag"].toInt(), key, val["value"]); {
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++) for(QJsonObject::iterator it = _opAttrs.begin(); it != _opAttrs.end(); it++)
{ // encode any remaining op-attrs { // encode any remaining op-attrs
@ -339,7 +362,25 @@ void IppMsg::encode_attr(Bytestream& msg, quint8 tag, QString name, QJsonValue v
name = ""; name = "";
} }
msg << tag << quint16(name.length()) << name.toStdString(); 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) { switch (tag) {
case OpAttrs: case OpAttrs:
@ -356,7 +397,7 @@ void IppMsg::encode_attr(Bytestream& msg, quint8 tag, QString name, QJsonValue v
} }
case Boolean: case Boolean:
{ {
quint32 tmp_u8 = value.toBool(); quint8 tmp_u8 = value.toBool();
msg << (quint16)1 << tmp_u8; msg << (quint16)1 << tmp_u8;
break; break;
} }
@ -383,19 +424,11 @@ void IppMsg::encode_attr(Bytestream& msg, quint8 tag, QString name, QJsonValue v
case BeginCollection: case BeginCollection:
{ {
msg << (quint16)0; // length of value msg << (quint16)0; // length of value
if(value.isObject()) QJsonObject collection = value.toObject();
for(QString key : collection.keys())
{ {
QJsonObject collection = value.toObject(); encode_attr(msg, collection[key].toObject()["tag"].toInt(), key,
foreach(QString key, collection.keys()) collection[key].toObject()["value"], true);
{
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; msg << (quint8)EndCollection << (quint16)0 << (quint16)0;
break; break;
@ -423,3 +456,8 @@ void IppMsg::encode_attr(Bytestream& msg, quint8 tag, QString name, QJsonValue v
break; 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 "bytestream.h"
#include <QObject> #include <QObject>
#include <QJsonObject>
#include <QJsonArray>
#include <QDate>
#include <QTime>
#include <QDebug> #include <QDebug>
#include <QUrl>
#include <QtNetwork>
#include <QNetworkReply>
#include <QIODevice>
class IppMsg class IppMsg
{ {
@ -61,20 +61,23 @@ public:
RestartJob = 0x000E, RestartJob = 0x000E,
PausePrinter = 0x0010, PausePrinter = 0x0010,
ResumePrinter = 0x0011, ResumePrinter = 0x0011,
PurgeJobs = 0x0012 PurgeJobs = 0x0012,
IdentifyPrinter = 0x003C
}; };
explicit IppMsg(); explicit IppMsg();
explicit IppMsg(Bytestream& resp); 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(const IppMsg& other) = default;
~IppMsg(); ~IppMsg();
Bytestream encode(Operation op); Bytestream encode();
QJsonObject getPrinterAttrs() {return _printerAttrs;} QJsonObject getPrinterAttrs() {return _printerAttrs;}
QJsonArray getJobAttrs() {return _jobAttrs;} QJsonArray getJobAttrs() {return _jobAttrs;}
QJsonObject getOpAttrs() {return _opAttrs;} QJsonObject getOpAttrs() {return _opAttrs;}
void setOpAttr(QString name, IppMsg::IppTag type, QJsonValue value);
quint16 getStatus() {return _status;} quint16 getStatus() {return _status;}
@ -85,6 +88,9 @@ private:
QJsonValue collect_attributes(QJsonArray& attrs); QJsonValue collect_attributes(QJsonArray& attrs);
QString consume_attribute(QJsonObject& attrs, Bytestream& data); QString consume_attribute(QJsonObject& attrs, Bytestream& data);
void encode_attr(Bytestream& msg, quint8 tag, QString name, QJsonValue value, bool subCollection=false); 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 _majVsn;
quint8 _minVsn; quint8 _minVsn;

View file

@ -1,42 +1,41 @@
#include "ippprinter.h" #include "ippprinter.h"
#include "ippdiscovery.h"
#include "mimer.h" #include "mimer.h"
#include "convertchecker.h"
#include "papersizes.h" #include "papersizes.h"
#include "overrider.h" #include "overrider.h"
#include "settings.h" #include "settings.h"
#include <fstream>
Q_DECLARE_METATYPE(QMargins) Q_DECLARE_METATYPE(QMargins)
Q_DECLARE_METATYPE(IppMsg)
IppPrinter::IppPrinter() IppPrinter::IppPrinter()
{ {
_worker = new PrinterWorker(this);
QObject::connect(this, &IppPrinter::urlChanged, this, &IppPrinter::onUrlChanged); QObject::connect(this, &IppPrinter::urlChanged, this, &IppPrinter::onUrlChanged);
qRegisterMetaType<QTemporaryFile*>("QTemporaryFile*"); qRegisterMetaType<QTemporaryFile*>("QTemporaryFile*");
_worker = new PrinterWorker(this);
_worker->moveToThread(&_workerThread);
connect(&_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
connect(this, &IppPrinter::doDoGetPrinterAttributes, _worker, &PrinterWorker::getPrinterAttributes); connect(this, &IppPrinter::doDoGetPrinterAttributes, _worker, &PrinterWorker::getPrinterAttributes);
connect(this, &IppPrinter::doGetJobs, _worker, &PrinterWorker::getJobs); connect(this, &IppPrinter::doGetJobs, _worker, &PrinterWorker::getJobs);
connect(this, &IppPrinter::doCancelJob, _worker, &PrinterWorker::cancelJob); connect(this, &IppPrinter::doCancelJob, _worker, &PrinterWorker::cancelJob);
connect(this, &IppPrinter::doJustUpload, _worker, &PrinterWorker::justUpload); connect(this, &IppPrinter::doIdentify, _worker, &PrinterWorker::identify);
connect(this, &IppPrinter::doPrint, _worker, &PrinterWorker::print);
connect(this, &IppPrinter::doPrint2, _worker, &PrinterWorker::print2);
connect(this, &IppPrinter::doGetStrings, _worker, &PrinterWorker::getStrings);
connect(this, &IppPrinter::doGetImage, _worker, &PrinterWorker::getImage);
connect(this, &IppPrinter::doConvertPdf, _worker, &PrinterWorker::convertPdf);
connect(this, &IppPrinter::doConvertImage, _worker, &PrinterWorker::convertImage);
connect(this, &IppPrinter::doConvertOfficeDocument, _worker, &PrinterWorker::convertOfficeDocument);
connect(this, &IppPrinter::doConvertPlaintext, _worker, &PrinterWorker::convertPlaintext);
connect(_worker, &PrinterWorker::progress, this, &IppPrinter::setProgress); connect(_worker, &PrinterWorker::progress, this, &IppPrinter::setProgress);
connect(_worker, &PrinterWorker::busyMessage, this, &IppPrinter::setBusyMessage); connect(_worker, &PrinterWorker::busyMessage, this, &IppPrinter::setBusyMessage);
connect(_worker, &PrinterWorker::failed, this, &IppPrinter::convertFailed); connect(_worker, &PrinterWorker::failed, this, &IppPrinter::convertFailed);
qRegisterMetaType<QMargins>(); qRegisterMetaType<QMargins>();
qRegisterMetaType<IppMsg>();
_workerThread.start();
} }
IppPrinter::~IppPrinter() { IppPrinter::~IppPrinter() {
// TODO: delete worker and workerthread?
} }
QJsonObject IppPrinter::opAttrs() { QJsonObject IppPrinter::opAttrs() {
@ -55,8 +54,6 @@ void IppPrinter::setUrl(QString url_s)
{ {
QUrl url = QUrl(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 not already a good scheme, try to fixup, or give an empty url
if(url.scheme() != "ipp" && url.scheme() != "ipps" && url.scheme() != "file") if(url.scheme() != "ipp" && url.scheme() != "ipps" && url.scheme() != "file")
{ {
@ -86,44 +83,110 @@ void IppPrinter::setUrl(QString url_s)
void IppPrinter::onUrlChanged() void IppPrinter::onUrlChanged()
{ {
QMetaObject::invokeMethod(_worker, "urlChanged", Q_ARG(QUrl, httpUrl()));
refresh(); refresh();
} }
void IppPrinter::refresh() { void IppPrinter::refresh() {
// _attrs = QJsonObject();
// emit attrsChanged();
// _additionalDocumentFormats = QStringList();
// emit additionalDocumentFormatsChanged();
if(_url.scheme() == "file") if(_url.scheme() == "file")
{ {
_attrs = QJsonObject(); _attrs = QJsonObject();
QString localFile = _url.toLocalFile();
QFile file(_url.toLocalFile()); if(localFile.endsWith(".raw") || localFile.endsWith(".bin"))
if(file.open(QIODevice::ReadOnly))
{ {
QJsonDocument JsonDocument = QJsonDocument::fromJson(file.readAll()); try
{
_attrs = JsonDocument.object(); std::ifstream ifs = std::ifstream(localFile.toStdString(), std::ios::in | std::ios::binary);
// These won't load anyway...r Bytestream InBts(ifs);
_attrs.remove("printer-icons"); IppMsg resp(InBts);
file.close(); qDebug() << resp.getStatus() << resp.getOpAttrs() << resp.getPrinterAttrs();
Overrider::instance()->apply(_attrs); _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();
file.close();
}
}
Overrider::instance()->apply(_attrs);
emit attrsChanged(); emit attrsChanged();
// For testing fake file-prinetrs with a strings file hosted elsewhere
// MaybeGetIcon();
// MaybeGetStrings();
UpdateAdditionalDocumentFormats(); UpdateAdditionalDocumentFormats();
} }
else else
{ {
QJsonObject o = opAttrs(); QJsonObject o = opAttrs();
IppMsg msg = IppMsg(o); IppMsg msg = IppMsg(IppMsg::GetPrinterAttrs, o);
emit doDoGetPrinterAttributes(msg.encode(IppMsg::GetPrinterAttrs)); 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() void IppPrinter::UpdateAdditionalDocumentFormats()
{ {
_additionalDocumentFormats = QStringList(); _additionalDocumentFormats = QStringList();
@ -175,6 +238,8 @@ void IppPrinter::getPrinterAttributesFinished(CURLcode res, Bytestream data)
emit attrsChanged(); emit attrsChanged();
MaybeGetStrings();
MaybeGetIcon();
UpdateAdditionalDocumentFormats(); UpdateAdditionalDocumentFormats();
} }
@ -247,14 +312,48 @@ void IppPrinter::cancelJobFinished(CURLcode res, Bytestream data)
getJobs(); getJobs();
} }
void IppPrinter::identifyFinished(CURLcode /*res*/, Bytestream /*data*/)
void IppPrinter::ignoreSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
{ {
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(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) else if(mimeType == Mimer::Postscript)
{ {
return firstMatch(supportedMimeTypes, {Mimer::Postscript}); return firstMatch(supportedMimeTypes, {Mimer::Postscript});
} }
else if(mimeType == Mimer::Plaintext)
{
return firstMatch(supportedMimeTypes, PdfPrioList << Mimer::Plaintext);
}
else if(Mimer::isOffice(mimeType)) 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)) else if(Mimer::isImage(mimeType))
{ {
@ -310,29 +419,28 @@ QString targetFormatIfAuto(QString documentFormat, QString mimeType, QJsonArray
return documentFormat; return documentFormat;
} }
void IppPrinter::adjustRasterSettings(QString documentFormat, QJsonObject& jobAttrs, quint32& HwResX, quint32& HwResY, void IppPrinter::adjustRasterSettings(QString filename, QString mimeType, QJsonObject& jobAttrs, PrintParameters& Params)
bool& BackHFlip, bool& BackVFlip)
{ {
if(documentFormat != Mimer::PWG && documentFormat != Mimer::URF) if(Params.format != PrintParameters::PWG && Params.format != PrintParameters::URF)
{ {
return; 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 diff = std::numeric_limits<quint32>::max();
quint32 AdjustedHwResX = HwResX; quint32 AdjustedHwResX = Params.hwResW;
quint32 AdjustedHwResY = HwResY; quint32 AdjustedHwResY = Params.hwResH;
foreach(QJsonValue res, _attrs["pwg-raster-document-resolution-supported"].toObject()["value"].toArray()) for(QJsonValue res : _attrs["pwg-raster-document-resolution-supported"].toObject()["value"].toArray())
{ {
QJsonObject resObj = res.toObject(); QJsonObject resObj = res.toObject();
if(resObj["units"] != 3) if(resObj["units"] != 3)
{ {
continue; 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) if(tmpDiff < diff)
{ {
diff = tmpDiff; diff = tmpDiff;
@ -340,27 +448,26 @@ void IppPrinter::adjustRasterSettings(QString documentFormat, QJsonObject& jobAt
AdjustedHwResY = resObj["y"].toInt(); AdjustedHwResY = resObj["y"].toInt();
} }
} }
HwResX = AdjustedHwResX; Params.hwResW = AdjustedHwResX;
HwResY = AdjustedHwResY; Params.hwResH = AdjustedHwResY;
} }
else if(Params.format == PrintParameters::URF)
if(documentFormat == Mimer::URF) { // Ensure Params.format resolution for URF
{ // Ensure symmetric resolution for URF Params.hwResW = Params.hwResH = std::min(Params.hwResW, Params.hwResH);
HwResX = HwResY = std::min(HwResX, HwResY);
quint32 diff = std::numeric_limits<quint32>::max(); quint32 diff = std::numeric_limits<quint32>::max();
quint32 AdjustedHwRes = HwResX; quint32 AdjustedHwRes = Params.hwResW;
QJsonArray urfSupported = _attrs["urf-supported"].toObject()["value"].toArray(); QJsonArray urfSupported = _attrs["urf-supported"].toObject()["value"].toArray();
foreach(QJsonValue us, urfSupported) for(QJsonValue us : urfSupported)
{ {
if(us.toString().startsWith("RS")) if(us.toString().startsWith("RS"))
{ //RS300[-600] { //RS300[-600]
QStringList resolutions = us.toString().mid(2).split("-"); QStringList resolutions = us.toString().mid(2).split("-");
foreach(QString res, resolutions) for(QString res : resolutions)
{ {
int intRes = res.toInt(); int intRes = res.toInt();
quint32 tmpDiff = std::abs(int(HwResX - intRes)); quint32 tmpDiff = std::abs(int(Params.hwResW - intRes));
if(tmpDiff < diff) if(tmpDiff < diff)
{ {
diff = tmpDiff; diff = tmpDiff;
@ -368,7 +475,7 @@ void IppPrinter::adjustRasterSettings(QString documentFormat, QJsonObject& jobAt
} }
} }
HwResX = HwResY = AdjustedHwRes; Params.hwResW = Params.hwResH = AdjustedHwRes;
break; break;
} }
} }
@ -378,64 +485,105 @@ void IppPrinter::adjustRasterSettings(QString documentFormat, QJsonObject& jobAt
if(Sides != "" && Sides != "one-sided") 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(); QString DocumentSheetBack = _attrs["pwg-raster-document-sheet-back"].toObject()["value"].toString();
if(Sides=="two-sided-long-edge") if(Sides=="two-sided-long-edge")
{ {
if(DocumentSheetBack=="flipped") if(DocumentSheetBack=="flipped")
{ {
BackVFlip=true; Params.backVFlip=true;
} }
else if(DocumentSheetBack=="rotated") else if(DocumentSheetBack=="rotated")
{ {
BackHFlip=true; Params.backHFlip=true;
BackVFlip=true; Params.backVFlip=true;
} }
} }
else if(Sides=="two-sided-short-edge") else if(Sides=="two-sided-short-edge")
{ {
if(DocumentSheetBack=="flipped") if(DocumentSheetBack=="flipped")
{ {
BackHFlip=true; Params.backHFlip=true;
} }
else if(DocumentSheetBack=="manual-tumble") else if(DocumentSheetBack=="manual-tumble")
{ {
BackHFlip=true; Params.backHFlip=true;
BackVFlip=true; Params.backVFlip=true;
} }
} }
} }
else if(documentFormat == Mimer::URF) else if(Params.format == PrintParameters::URF)
{ {
QJsonArray URfSupported = _attrs["urf-supported"].toObject()["value"].toArray(); QJsonArray URfSupported = _attrs["urf-supported"].toObject()["value"].toArray();
if(Sides=="two-sided-long-edge") if(Sides=="two-sided-long-edge")
{ {
if(URfSupported.contains("DM2")) if(URfSupported.contains("DM2"))
{ {
BackVFlip=true; Params.backVFlip=true;
} }
else if(URfSupported.contains("DM3")) else if(URfSupported.contains("DM3"))
{ {
BackHFlip=true; Params.backHFlip=true;
BackVFlip=true; Params.backVFlip=true;
} }
} }
else if(Sides=="two-sided-short-edge") else if(Sides=="two-sided-short-edge")
{ {
if(URfSupported.contains("DM2")) if(URfSupported.contains("DM2"))
{ {
BackHFlip=true; Params.backHFlip=true;
} }
else if(URfSupported.contains("DM4")) else if(URfSupported.contains("DM4"))
{ {
BackHFlip=true; Params.backHFlip=true;
BackVFlip=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) void IppPrinter::print(QJsonObject jobAttrs, QString filename)
@ -445,6 +593,7 @@ void IppPrinter::print(QJsonObject jobAttrs, QString filename)
_progress = ""; _progress = "";
emit progressChanged(); emit progressChanged();
PrintParameters Params;
QFileInfo fileinfo(filename); QFileInfo fileinfo(filename);
if(!fileinfo.exists()) if(!fileinfo.exists())
@ -465,22 +614,48 @@ void IppPrinter::print(QJsonObject jobAttrs, QString filename)
qDebug() << supportedMimeTypes << supportedMimeTypes.contains(mimeType); qDebug() << supportedMimeTypes << supportedMimeTypes.contains(mimeType);
QJsonObject o = opAttrs(); if(!Params.setPaperSize(getAttrOrDefault(jobAttrs, "media").toString(Params.paperSizeName.c_str()).toStdString()))
o.insert("job-name", QJsonObject {{"tag", IppMsg::NameWithoutLanguage}, {"value", fileinfo.fileName()}}); {
emit convertFailed(tr("Unsupported paper size"));
return;
}
QString PaperSize = getAttrOrDefault(jobAttrs, "media").toString(); 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")) if(jobAttrs.contains("media-col") && jobAttrs.contains("media"))
{ {
qDebug() << "moving media to media-col" << PaperSize; qDebug() << "moving media to media-col" << Params.paperSizeName.c_str();
if(!PaperSizes.contains(PaperSize))
{
emit convertFailed(tr("Unknown document format dimensions"));
return;
}
int x = PaperSizes[PaperSize].width()*100; int x = Params.getPaperSizeWInMillimeters()*100;
int y = PaperSizes[PaperSize].height()*100; int y = Params.getPaperSizeHInMillimeters()*100;
QJsonObject Dimensions = QJsonObject Dimensions =
{{"tag", IppMsg::BeginCollection}, {{"tag", IppMsg::BeginCollection},
@ -499,125 +674,114 @@ void IppPrinter::print(QJsonObject jobAttrs, QString filename)
jobAttrs.remove("media"); jobAttrs.remove("media");
} }
QString documentFormat = getAttrOrDefault(jobAttrs, "document-format").toString(); QJsonObject jobOpAttrs = opAttrs();
qDebug() << "target format:" << documentFormat;
// document-format goes in the op-attrs and not the job-attrs // 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"); jobAttrs.remove("document-format");
documentFormat = targetFormatIfAuto(documentFormat, mimeType, supportedMimeTypes); targetFormat = targetFormatIfAuto(targetFormat, mimeType, supportedMimeTypes);
qDebug() << "adjusted target format:" << documentFormat; qDebug() << "adjusted target format:" << targetFormat;
if(documentFormat == "" || documentFormat == Mimer::OctetStream) if(targetFormat == "" || targetFormat == Mimer::OctetStream)
{ {
emit convertFailed(tr("Unknown document format")); emit convertFailed(tr("Unknown document format"));
return; return;
} }
qDebug() << "Printing job" << o << jobAttrs; if(targetFormat == Mimer::PDF)
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"))
{ {
QJsonObject PageRanges = getAttrOrDefault(jobAttrs, "page-ranges").toObject(); Params.format = PrintParameters::PDF;
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");
}
} }
else if(targetFormat == Mimer::Postscript)
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))
{ {
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 else
{ {
if(PaperSize == "") Params.format = PrintParameters::Invalid;
{ }
PaperSize = "iso_a4_210x297mm";
} qDebug() << "Printing job" << jobOpAttrs << jobAttrs;
else if(!PaperSizes.contains(PaperSize))
{ QJsonValue PrinterResolutionRef = getAttrOrDefault(jobAttrs, "printer-resolution");
emit convertFailed(tr("Unsupported print media")); Params.hwResW = PrinterResolutionRef.toObject()["x"].toInt(Params.hwResW);
return; Params.hwResH = PrinterResolutionRef.toObject()["y"].toInt(Params.hwResH);
}
// Effected locally, unless it is Postscript which we cant't render
bool TwoSided = false; if(jobAttrs.contains("page-ranges") && mimeType != Mimer::Postscript)
bool Tumble = false; {
QString Sides = getAttrOrDefault(jobAttrs, "sides").toString(); QJsonArray tmp;
QJsonValue pageRanges = getAttrOrDefault(jobAttrs, "page-ranges");
if(Sides=="two-sided-long-edge") if(pageRanges.isArray())
{ {
TwoSided = true; tmp = pageRanges.toArray();
} }
else if(Sides=="two-sided-short-edge") else if(pageRanges.isObject())
{ {
TwoSided = true; tmp = {pageRanges.toObject()};
Tumble = true; }
} for(QJsonValueRef ref : tmp)
{
setBusyMessage(tr("Preparing")); Params.pageRangeList.push_back({ref.toObject()["low"].toInt(), ref.toObject()["high"].toInt()});
}
if(mimeType == Mimer::PDF) jobAttrs.remove("page-ranges");
{ }
emit doConvertPdf(filename, contents, documentFormat, Colors, Quality,
PaperSize, HwResX, HwResY, TwoSided, Tumble, PageRangeLow, PageRangeHigh, adjustRasterSettings(filename, mimeType, jobAttrs, Params);
BackHFlip, BackVFlip);
} QString Sides = getAttrOrDefault(jobAttrs, "sides").toString();
else if(mimeType == Mimer::Plaintext)
{ if(Sides=="two-sided-long-edge")
emit doConvertPlaintext(filename, contents, documentFormat, Colors, Quality, {
PaperSize, HwResX, HwResY, TwoSided, Tumble, BackHFlip, BackVFlip); Params.duplex = true;
} }
else if (Mimer::isImage(mimeType)) else if(Sides=="two-sided-short-edge")
{ {
QMargins margins(getAttrOrDefault(jobAttrs, "media-left-margin", "media-col").toInt(), Params.duplex = true;
getAttrOrDefault(jobAttrs, "media-top-margin", "media-col").toInt(), Params.tumble = true;
getAttrOrDefault(jobAttrs, "media-right-margin", "media-col").toInt(), }
getAttrOrDefault(jobAttrs, "media-bottom-margin", "media-col").toInt());
Params.quality = getAttrOrDefault(jobAttrs, "print-quality").toInt();
emit doConvertImage(filename, contents, documentFormat, Colors, Quality,
PaperSize, HwResX, HwResY, margins); QString PrintColorMode = getAttrOrDefault(jobAttrs, "print-color-mode").toString();
} Params.colors = PrintColorMode.contains("color") ? 3 : PrintColorMode.contains("monochrome") ? 1 : Params.colors;
else if(Mimer::isOffice(mimeType))
{ QJsonArray supportedOperations = _attrs["operations-supported"].toObject()["value"].toArray();
emit doConvertOfficeDocument(filename, contents, documentFormat, Colors, Quality,
PaperSize, HwResX, HwResY, TwoSided, Tumble, PageRangeLow, PageRangeHigh, if(supportedOperations.contains(IppMsg::CreateJob) && supportedOperations.contains(IppMsg::SendDocument))
BackHFlip, BackVFlip); {
} QJsonObject createJobOpAttrs = opAttrs();
else createJobOpAttrs.insert("job-name", QJsonObject {{"tag", IppMsg::NameWithoutLanguage}, {"value", fileinfo.fileName()}});
{
emit convertFailed(tr("Cannot convert this file format")); qDebug() << "Final create op attributes:" << createJobOpAttrs;
return; 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
{
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() { bool IppPrinter::getJobs() {
@ -626,9 +790,9 @@ bool IppPrinter::getJobs() {
QJsonObject o = opAttrs(); QJsonObject o = opAttrs();
o.insert("requested-attributes", QJsonObject {{"tag", IppMsg::Keyword}, {"value", "all"}}); 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; return true;
} }
@ -640,16 +804,29 @@ bool IppPrinter::cancelJob(qint32 jobId) {
QJsonObject o = opAttrs(); QJsonObject o = opAttrs();
o.insert("job-id", QJsonObject {{"tag", IppMsg::Integer}, {"value", jobId}}); 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; return true;
} }
bool IppPrinter::correctSuffix() 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()); QUrl url(u.toString());
if(url.path() == _url.path()) if(url.path() == _url.path())
@ -663,7 +840,7 @@ bool IppPrinter::correctSuffix()
QStringList IppPrinter::suffixes() QStringList IppPrinter::suffixes()
{ {
QStringList res; 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()); QUrl url(u.toString());
if(!res.contains(url.path())) if(!res.contains(url.path()))
@ -711,6 +888,13 @@ void IppPrinter::setProgress(qint64 sent, qint64 total)
emit progressChanged(); 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) QJsonValue IppPrinter::getAttrOrDefault(QJsonObject jobAttrs, QString name, QString subkey)
{ {
if(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") && if(_attrs.contains("ipp-versions-supported") &&
_attrs["ipp-versions-supported"].toObject()["value"].toArray().contains("2.0")) _attrs["ipp-versions-supported"].toObject()["value"].toArray().contains("2.0"))
{ {
qDebug() << "TWO-POINT-ZERO"; 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,12 +1,11 @@
#ifndef IPPPRINTER_H #ifndef IPPPRINTER_H
#define IPPPRINTER_H #define IPPPRINTER_H
#include <QtNetwork>
#include <QNetworkAccessManager>
#include "ippmsg.h" #include "ippmsg.h"
#include "printerworker.h" #include "printerworker.h"
#include "curlrequester.h" #include "curlrequester.h"
#include <mlite5/MGConfItem> #include <QImage>
class IppPrinter : public QObject class IppPrinter : public QObject
{ {
@ -18,6 +17,8 @@ class IppPrinter : public QObject
Q_PROPERTY(QJsonObject attrs MEMBER _attrs NOTIFY attrsChanged) Q_PROPERTY(QJsonObject attrs MEMBER _attrs NOTIFY attrsChanged)
Q_PROPERTY(QJsonObject jobAttrs MEMBER _jobAttrs NOTIFY jobAttrsChanged) Q_PROPERTY(QJsonObject jobAttrs MEMBER _jobAttrs NOTIFY jobAttrsChanged)
Q_PROPERTY(QJsonArray jobs MEMBER _jobs NOTIFY jobsChanged) 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(QStringList additionalDocumentFormats MEMBER _additionalDocumentFormats NOTIFY additionalDocumentFormatsChanged)
Q_PROPERTY(QString busyMessage MEMBER _busyMessage NOTIFY busyMessageChanged) Q_PROPERTY(QString busyMessage MEMBER _busyMessage NOTIFY busyMessageChanged)
Q_PROPERTY(QString progress MEMBER _progress NOTIFY progressChanged) Q_PROPERTY(QString progress MEMBER _progress NOTIFY progressChanged)
@ -37,6 +38,7 @@ public:
Q_INVOKABLE bool getJobs(); Q_INVOKABLE bool getJobs();
Q_INVOKABLE bool cancelJob(qint32 jobId); Q_INVOKABLE bool cancelJob(qint32 jobId);
Q_INVOKABLE bool identify();
bool correctSuffix(); bool correctSuffix();
QStringList suffixes(); QStringList suffixes();
@ -47,32 +49,34 @@ signals:
void jobAttrsChanged(); void jobAttrsChanged();
void jobsChanged(); void jobsChanged();
void stringsChanged();
void iconChanged();
void jobFinished(bool status); void jobFinished(bool status);
void cancelStatus(bool status); void cancelStatus(bool status);
void doDoGetPrinterAttributes(Bytestream msg); void doDoGetPrinterAttributes(Bytestream msg);
void doGetJobs(Bytestream msg); void doGetJobs(Bytestream msg);
void doCancelJob(Bytestream msg); void doCancelJob(Bytestream msg);
void doIdentify(Bytestream msg);
void doJustUpload(QString filename, Bytestream header); 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, void doConvertPdf(QString filename, Bytestream header, PrintParameters Params);
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 doConvertImage(QString filename, Bytestream header, void doConvertImage(QString filename, Bytestream header, PrintParameters Params, QMargins margins);
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, QMargins margins);
void doConvertOfficeDocument(QString filename, Bytestream header, void doConvertOfficeDocument(QString filename, Bytestream header, PrintParameters Params);
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 doConvertPlaintext(QString filename, Bytestream header, void doConvertPlaintext(QString filename, Bytestream header, PrintParameters Params);
QString targetFormat, quint32 Colors, quint32 Quality, QString PaperSize,
quint32 HwResX, quint32 HwResY, bool TwoSided, bool Tumble, bool BackHFlip, bool BackVFlip); 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 additionalDocumentFormatsChanged();
void busyMessageChanged(); void busyMessageChanged();
@ -81,46 +85,54 @@ signals:
public slots: public slots:
void print(QJsonObject attrs, QString file); void print(QJsonObject attrs, QString file);
void onUrlChanged(); void onUrlChanged();
void MaybeGetStrings();
void MaybeGetIcon(bool retry=false);
void UpdateAdditionalDocumentFormats(); void UpdateAdditionalDocumentFormats();
void getPrinterAttributesFinished(CURLcode res, Bytestream data); void getPrinterAttributesFinished(CURLcode res, Bytestream data);
void printRequestFinished(CURLcode res, Bytestream data); void printRequestFinished(CURLcode res, Bytestream data);
void getJobsRequestFinished(CURLcode res, Bytestream data); void getJobsRequestFinished(CURLcode res, Bytestream data);
void cancelJobFinished(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); void convertFailed(QString message);
private: private:
QUrl _url; QUrl _url;
QUrl httpUrl(); QUrl httpUrl();
void resolveUrl(QUrl& url);
QJsonObject opAttrs(); QJsonObject opAttrs();
void adjustRasterSettings(QString documentFormat, QJsonObject& jobAttrs, quint32& HwResX, quint32& HwResY, void adjustRasterSettings(QString filename, QString mimeType, QJsonObject& jobAttrs, PrintParameters& Params);
bool& BackHFlip, bool& BackVFlip);
void setBusyMessage(QString msg); void setBusyMessage(QString msg);
void setProgress(qint64 sent, qint64 total); void setProgress(qint64 sent, qint64 total);
bool isAllowedAddress(QUrl addr);
QJsonValue getAttrOrDefault(QJsonObject jobAttrs, QString name, QString subkey = ""); 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 _attrs;
QJsonObject _jobAttrs; QJsonObject _jobAttrs;
QJsonArray _jobs; QJsonArray _jobs;
QJsonObject _strings;
QImage _icon;
QStringList _additionalDocumentFormats; QStringList _additionalDocumentFormats;
QString _busyMessage; QString _busyMessage;
QString _progress; QString _progress;
QThread _workerThread;
PrinterWorker* _worker; PrinterWorker* _worker;
bool _iconRetried = false;
}; };
#endif // IPPPRINTER_H #endif // IPPPRINTER_H

View file

@ -1,4 +1,5 @@
#include "mimer.h" #include "mimer.h"
#include <QDebug>
const QString Mimer::OctetStream = "application/octet-stream"; const QString Mimer::OctetStream = "application/octet-stream";
@ -11,6 +12,8 @@ const QString Mimer::PNG = "image/png";
const QString Mimer::GIF = "image/gif"; const QString Mimer::GIF = "image/gif";
const QString Mimer::JPEG = "image/jpeg"; const QString Mimer::JPEG = "image/jpeg";
const QString Mimer::TIFF = "image/tiff"; 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::DOC = "application/msword";
const QString Mimer::DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; const QString Mimer::DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
@ -23,6 +26,7 @@ const QString Mimer::ODP = "application/vnd.oasis.opendocument.presentation";
const QString Mimer::Plaintext = "text/plain"; const QString Mimer::Plaintext = "text/plain";
const QStringList Mimer::RasterFormats = {PWG, URF};
const QStringList Mimer::OfficeFormats = {DOC, DOCX, RTF, RTF_APP, ODT, PPT, PPTX, ODP}; const QStringList Mimer::OfficeFormats = {DOC, DOCX, RTF, RTF_APP, ODT, PPT, PPTX, ODP};
Mimer::Mimer() Mimer::Mimer()
@ -49,5 +53,14 @@ Mimer* Mimer::instance()
} }
QString Mimer::get_type(QString filename) { 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,6 +24,8 @@ public:
Q_PROPERTY(const QString GIF MEMBER GIF CONSTANT); Q_PROPERTY(const QString GIF MEMBER GIF CONSTANT);
Q_PROPERTY(const QString JPEG MEMBER JPEG CONSTANT); Q_PROPERTY(const QString JPEG MEMBER JPEG CONSTANT);
Q_PROPERTY(const QString TIFF MEMBER TIFF 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 DOC MEMBER DOC CONSTANT);
Q_PROPERTY(const QString DOCX MEMBER DOCX CONSTANT); Q_PROPERTY(const QString DOCX MEMBER DOCX CONSTANT);
@ -49,6 +51,8 @@ public:
static const QString GIF; static const QString GIF;
static const QString JPEG; static const QString JPEG;
static const QString TIFF; static const QString TIFF;
static const QString SVG;
static const QString RBMP;
static const QString DOC; static const QString DOC;
static const QString DOCX; static const QString DOCX;
@ -61,11 +65,16 @@ public:
static const QString Plaintext; static const QString Plaintext;
static const QStringList RasterFormats;
static const QStringList OfficeFormats; static const QStringList OfficeFormats;
Q_INVOKABLE static bool isImage(QString mimeType) 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) Q_INVOKABLE static bool isOffice(QString mimeType)
{ {

View file

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

View file

@ -3,208 +3,6 @@
#include <QMap> #include <QMap>
#include <QSizeF> #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 = static QMap<QString, QString> CalligraPaperSizes =
{{"iso_a0_841x1189mm", "A0"}, {{"iso_a0_841x1189mm", "A0"},
{"iso_a1_594x841mm", "A1"}, {"iso_a1_594x841mm", "A1"},

View file

@ -1,5 +1,4 @@
#include "printerworker.h" #include "printerworker.h"
#include <sailfishapp.h>
#include "papersizes.h" #include "papersizes.h"
#include "convertchecker.h" #include "convertchecker.h"
#include "mimer.h" #include "mimer.h"
@ -9,45 +8,188 @@
#include <QTextDocument> #include <QTextDocument>
#include <QPdfWriter> #include <QPdfWriter>
#include <QAbstractTextDocumentLayout> #include <QAbstractTextDocumentLayout>
#include <QtSvg>
#include "ippprinter.h" #include "ippprinter.h"
#include "pdf2printable.h" #include "pdf2printable.h"
#include "ppm2pwg.h" #include "ppm2pwg.h"
#include "baselinify.h"
#include <fstream>
#include <iostream>
#define OK(call) if(!(call)) throw ConvertFailedException() #define OK(call) if(!(call)) throw ConvertFailedException()
PrinterWorker::PrinterWorker(IppPrinter* parent) PrinterWorker::PrinterWorker(IppPrinter* parent)
{ {
_printer = 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) void PrinterWorker::getPrinterAttributes(Bytestream msg)
{ {
CurlRequester cr(_printer->httpUrl()); CurlRequester cr(_url, CurlRequester::IppRequest, &msg);
cr.setFinishedCallback(_printer, &IppPrinter::getPrinterAttributesFinished); awaitResult(cr, "getPrinterAttributesFinished");
cr.write((char*)msg.raw(), msg.size());
} }
void PrinterWorker::getJobs(Bytestream msg) void PrinterWorker::getJobs(Bytestream msg)
{ {
CurlRequester cr(_printer->httpUrl()); CurlRequester cr(_url, CurlRequester::IppRequest, &msg);
cr.setFinishedCallback(_printer, &IppPrinter::getJobsRequestFinished); awaitResult(cr, "getJobsRequestFinished");
cr.write((char*)msg.raw(), msg.size());
} }
void PrinterWorker::cancelJob(Bytestream msg) void PrinterWorker::cancelJob(Bytestream msg)
{ {
CurlRequester cr(_printer->httpUrl()); CurlRequester cr(_url, CurlRequester::IppRequest, &msg);
cr.setFinishedCallback(_printer, &IppPrinter::cancelJobFinished); awaitResult(cr, "cancelJobFinished");
cr.write((char*)msg.raw(), msg.size()); }
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) void PrinterWorker::justUpload(QString filename, Bytestream header)
{ {
try {
emit busyMessage(tr("Printing")); emit busyMessage(tr("Printing"));
CurlRequester cr(_printer->httpUrl()); CurlRequester cr(_url);
cr.setFinishedCallback(_printer, &IppPrinter::printRequestFinished);
QFile file(filename); QFile file(filename);
file.open(QFile::ReadOnly); file.open(QFile::ReadOnly);
@ -56,51 +198,145 @@ try {
QByteArray tmp = file.readAll(); QByteArray tmp = file.readAll();
OK(cr.write(tmp.data(), tmp.length())); OK(cr.write(tmp.data(), tmp.length()));
file.close(); file.close();
awaitResult(cr, "printRequestFinished");
} }
catch(const ConvertFailedException& e)
void PrinterWorker::printImageAsImage(QString filename, Bytestream header, QString targetFormat)
{ {
emit failed(e.what() == QString("") ? tr("Upload error") : e.what()); QString imageFormat = "";
} QStringList supportedImageFormats = {Mimer::JPEG, Mimer::PNG};
}
void PrinterWorker::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::RBMP)
if(targetFormat == Mimer::URF)
{ {
format = Format::URF; // ok
} }
else if(targetFormat == Mimer::PWG) else if(supportedImageFormats.contains(targetFormat))
{ {
format = Format::PWG; imageFormat = targetFormat.split("/")[1];
}
else if(targetFormat == Mimer::Postscript)
{
format = Format::Postscript;
}
else if (targetFormat == Mimer::PDF)
{
format = Format::PDF;
} }
else else
{ {
throw ConvertFailedException(tr("Unsupported target format")); throw ConvertFailedException(tr("Unknown target format"));
} }
if(Colors == 0) QString mimeType = Mimer::instance()->get_type(filename);
Bytestream OutBts;
CurlRequester cr(_url);
if(mimeType == Mimer::JPEG && targetFormat == Mimer::JPEG)
{ {
Colors = 3; 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());
} }
CurlRequester cr(_printer->httpUrl()); emit busyMessage(tr("Printing"));
cr.setFinishedCallback(_printer, &IppPrinter::printRequestFinished);
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())); OK(cr.write((char*)header.raw(), header.size()));
@ -116,131 +352,124 @@ try {
emit progress(page, total); emit progress(page, total);
}); });
if(!PaperSizes.contains(PaperSize)) bool verbose = QLoggingCategory::defaultCategory()->isDebugEnabled();
{
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, int res = pdf_to_printable(filename.toStdString(), WriteFun, Params, ProgressFun, verbose);
format, TwoSided, Tumble, BackHFlip, BackVFlip, PageRangeLow, PageRangeHigh, ProgressFun);
if(res != 0) if(res != 0)
{ {
throw ConvertFailedException(tr("Conversion failed")); throw ConvertFailedException(tr("Conversion failed"));
} }
awaitResult(cr, "printRequestFinished");
qDebug() << "Finished"; qDebug() << "Finished";
} }
catch(const ConvertFailedException& e)
void PrinterWorker::convertImage(QString filename, Bytestream header, PrintParameters Params, QMargins margins)
{ {
emit failed(e.what() == QString("") ? tr("Conversion error") : e.what()); QString mimeType = Mimer::instance()->get_type(filename);
}
}
void PrinterWorker::convertImage(QString filename, Bytestream header, if(Params.format == PrintParameters::URF && (Params.hwResW != Params.hwResH))
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 { // URF only supports symmetric resolutions
qDebug() << "Unsupported URF resolution" << PaperSize; qDebug() << "Unsupported URF resolution";
throw ConvertFailedException(tr("Unsupported resolution (dpi)")); throw ConvertFailedException(tr("Unsupported resolution (dpi)"));
} }
if(!PaperSizes.contains(PaperSize)) qDebug() << "Size is" << Params.getPaperSizeWInPixels() << "x" << Params.getPaperSizeHInPixels();
{
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; int leftMarginPx = (margins.left()/2540.0)*Params.hwResW;
int rightMarginPx = (margins.right()/2540.0)*Params.hwResW;
QImage inImage; int topMarginPx = (margins.top()/2540.0)*Params.hwResH;
if(!inImage.load(filename)) int bottomMarginPx = (margins.bottom()/2540.0)*Params.hwResH;
{
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 totalXMarginPx = leftMarginPx+rightMarginPx;
int totalYMarginPx = topMarginPx+bottomMarginPx; int totalYMarginPx = topMarginPx+bottomMarginPx;
inImage = inImage.scaled(Width-totalXMarginPx, Height-totalYMarginPx, size_t targetWidth = Params.getPaperSizeWInPixels()-totalXMarginPx;
Qt::KeepAspectRatio, Qt::SmoothTransformation); size_t targetHeight = Params.getPaperSizeHInPixels()-totalYMarginPx;
if(pdfOrPostscript) 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; QTemporaryFile tmpPdfFile;
tmpPdfFile.open(); tmpPdfFile.open();
QPdfWriter pdfWriter(tmpPdfFile.fileName()); QPdfWriter pdfWriter(tmpPdfFile.fileName());
pdfWriter.setCreator("SeaPrint " SEAPRINT_VERSION); pdfWriter.setCreator("SeaPrint " SEAPRINT_VERSION);
QPageSize pageSize(size, QPageSize::Millimeter); QPageSize pageSize({Params.getPaperSizeWInPoints(), Params.getPaperSizeHInPoints()}, QPageSize::Point);
pdfWriter.setPageSize(pageSize); pdfWriter.setPageSize(pageSize);
pdfWriter.setResolution(HwResX); pdfWriter.setResolution(Params.hwResH);
QPainter painter(&pdfWriter); QPainter painter(&pdfWriter);
int xOffset = ((pdfWriter.width()-totalXMarginPx)-inImage.width())/2 + leftMarginPx; int xOffset = ((pdfWriter.width()-totalXMarginPx)-inImage.width())/2 + leftMarginPx;
int yOffset = ((pdfWriter.height()-totalYMarginPx)-inImage.height())/2 + topMarginPx; int yOffset = ((pdfWriter.height()-totalYMarginPx)-inImage.height())/2 + topMarginPx;
painter.drawImage(xOffset, yOffset, inImage); painter.drawImage(xOffset, yOffset, inImage);
painter.end(); painter.end();
convertPdf(tmpPdfFile.fileName(), header, targetFormat, Colors, Quality, PaperSize, convertPdf(tmpPdfFile.fileName(), header, Params);
HwResX, HwResY, false, false, 0, 0, false, false);
} }
else else
{ {
QImage outImage = QImage(Width, Height, inImage.format()); 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); outImage.fill(Qt::white);
QPainter painter(&outImage); QPainter painter(&outImage);
int xOffset = ((outImage.width()-totalXMarginPx)-inImage.width())/2 + leftMarginPx; int xOffset = ((outImage.width()-totalXMarginPx)-inImage.width())/2 + leftMarginPx;
@ -250,79 +479,71 @@ try {
QBuffer buf; QBuffer buf;
buf.open(QIODevice::ReadWrite); buf.open(QIODevice::ReadWrite);
Bytestream outBts; Bytestream fileHdr, 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; if(inImage.allGray())
{
outImage.save(&buf, gray ? "pgm" : "ppm"); Params.colors = 1; // No need to waste space/bandwidth...
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()); outImage.save(&buf, Params.colors==1 ? "pgm" : "ppm");
cr.setFinishedCallback(_printer, &IppPrinter::printRequestFinished); 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*)header.raw(), header.size()));
OK(cr.write((char*)(outBts.raw()), outBts.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"; qDebug() << "posted";
} }
catch(const ConvertFailedException& e)
void PrinterWorker::convertOfficeDocument(QString filename, Bytestream header, PrintParameters Params)
{ {
emit failed(e.what() == QString("") ? tr("Conversion error") : e.what()); if(Params.format == PrintParameters::URF && (Params.hwResW != Params.hwResH))
}
}
void PrinterWorker::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 { // URF only supports symmetric resolutions
qDebug() << "Unsupported URF resolution" << PaperSize; qDebug() << "Unsupported URF resolution";
throw ConvertFailedException(tr("Unsupported resolution (dpi)")); throw ConvertFailedException(tr("Unsupported resolution (dpi)"));
} }
QString ShortPaperSize; QString ShortPaperSize;
if(CalligraPaperSizes.contains(PaperSize)) if(CalligraPaperSizes.contains(Params.paperSizeName.c_str()))
{ {
ShortPaperSize = CalligraPaperSizes[PaperSize]; ShortPaperSize = CalligraPaperSizes[Params.paperSizeName.c_str()];
} }
else else
{ {
qDebug() << "Unsupported PDF paper size" << PaperSize; qDebug() << "Unsupported PDF paper size" << Params.paperSizeName.c_str();
throw ConvertFailedException(tr("Unsupported PDF paper size")); throw ConvertFailedException(tr("Unsupported PDF paper size"));
} }
@ -359,53 +580,29 @@ try {
// qDebug() << CalligraConverter->readAllStandardError(); // qDebug() << CalligraConverter->readAllStandardError();
quint32 pages = ConvertChecker::instance()->pdfPages(tmpPdfFile.fileName()); convertPdf(tmpPdfFile.fileName(), header, Params);
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() << "posted"; qDebug() << "posted";
} }
catch(const ConvertFailedException& e)
void PrinterWorker::convertPlaintext(QString filename, Bytestream header, PrintParameters Params)
{ {
emit failed(e.what() == QString("") ? tr("Conversion error") : e.what());
}
}
void PrinterWorker::convertPlaintext(QString filename, Bytestream header, QPageSize pageSize;
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)) if(Params.paperSizeUnits == PrintParameters::Millimeters)
{ {
qDebug() << "Unsupported paper size" << PaperSize; 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")); throw ConvertFailedException(tr("Unsupported paper size"));
} }
QSizeF size = PaperSizes[PaperSize];
QFile inFile(filename); QFile inFile(filename);
if(!inFile.open(QIODevice::ReadOnly)) if(!inFile.open(QIODevice::ReadOnly))
@ -413,14 +610,14 @@ try {
throw ConvertFailedException(tr("Failed to open file")); throw ConvertFailedException(tr("Failed to open file"));
} }
quint32 resolution = std::min(HwResX, HwResY); quint32 resolution = std::min(Params.hwResW, Params.hwResH);
QTemporaryFile tmpPdfFile; QTemporaryFile tmpPdfFile;
tmpPdfFile.open(); tmpPdfFile.open();
QPdfWriter pdfWriter(tmpPdfFile.fileName()); QPdfWriter pdfWriter(tmpPdfFile.fileName());
pdfWriter.setCreator("SeaPrint " SEAPRINT_VERSION); pdfWriter.setCreator("SeaPrint " SEAPRINT_VERSION);
QPageSize pageSize(size, QPageSize::Millimeter);
pdfWriter.setPageSize(pageSize); pdfWriter.setPageSize(pageSize);
pdfWriter.setResolution(resolution); pdfWriter.setResolution(resolution);
@ -459,7 +656,6 @@ try {
doc.setDefaultFont(font); doc.setDefaultFont(font);
(void)doc.documentLayout(); // wat (void)doc.documentLayout(); // wat
// Needs to be before painter // Needs to be before painter
pdfWriter.setMargins({mmMargin, mmMargin, mmMargin, mmMargin}); pdfWriter.setMargins({mmMargin, mmMargin, mmMargin, mmMargin});
@ -495,7 +691,7 @@ try {
bool first = true; bool first = true;
int pageCount = 0; int pageCount = 0;
foreach(QString page, pages) for(QString page : pages)
{ {
if(!first) if(!first)
{ {
@ -513,6 +709,7 @@ try {
while(true) while(true)
{ {
painter.save();
painter.translate(body.left(), body.top() - p*body.height()); painter.translate(body.left(), body.top() - p*body.height());
QRectF view(0, p*body.height(), body.width(), body.height()); QRectF view(0, p*body.height(), body.width(), body.height());
painter.setClipRect(view); painter.setClipRect(view);
@ -521,6 +718,7 @@ try {
context.clip = view; context.clip = view;
context.palette.setColor(QPalette::Text, Qt::black); context.palette.setColor(QPalette::Text, Qt::black);
doc.documentLayout()->draw(&painter, context); doc.documentLayout()->draw(&painter, context);
painter.restore();
p++; p++;
pageCount++; pageCount++;
@ -534,15 +732,81 @@ try {
painter.end(); painter.end();
convertPdf(tmpPdfFile.fileName(), header, targetFormat, Colors, Quality, PaperSize, HwResX, HwResY, convertPdf(tmpPdfFile.fileName(), header, Params);
TwoSided, Tumble, 0, 0, BackHFlip, BackVFlip);
qDebug() << "Finished"; qDebug() << "Finished";
qDebug() << "posted"; qDebug() << "posted";
}
} void PrinterWorker::convertPlaintextLabel(QString filename, Bytestream header, PrintParameters Params)
catch(const ConvertFailedException& e)
{ {
emit failed(e.what() == QString("") ? tr("Conversion error") : e.what()); 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));
} }

View file

@ -1,22 +1,24 @@
#ifndef PRINTERWORKER_H #ifndef PRINTERWORKER_H
#define PRINTERWORKER_H #define PRINTERWORKER_H
#include <QObject> #include <QObject>
#include <QtNetwork> #include <memory>
#include "curlrequester.h" #include "curlrequester.h"
#include "ippmsg.h"
#include "ppm2pwg/printparameters.h"
class IppPrinter; class IppPrinter;
class ConvertFailedException: public std::exception class ConvertFailedException: public std::exception
{ {
private: private:
QString _msg; std::string _msg;
public: public:
ConvertFailedException(QString msg = "") : _msg(msg = msg) ConvertFailedException(QString msg = "") : _msg(msg.toStdString())
{ {
} }
virtual const char* what() const throw() virtual const char* what() const throw()
{ {
return _msg.toStdString().c_str(); return _msg.c_str();
} }
}; };
@ -26,45 +28,45 @@ class PrinterWorker : public QObject
public: public:
PrinterWorker(IppPrinter* parent); PrinterWorker(IppPrinter* parent);
~PrinterWorker();
private: private:
PrinterWorker(); PrinterWorker();
public slots: public slots:
void urlChanged(QUrl url);
void getStrings(QUrl url);
void getImage(QUrl url);
void getPrinterAttributes(Bytestream msg); void getPrinterAttributes(Bytestream msg);
void getJobs(Bytestream msg); void getJobs(Bytestream msg);
void cancelJob(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);
void justUpload(QString filename, Bytestream header); private slots:
void cleanup();
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: signals:
void done(QNetworkRequest request, QTemporaryFile* data);
void progress(qint64 done, qint64 pages); void progress(qint64 done, qint64 pages);
void busyMessage(QString msg); void busyMessage(QString msg);
void failed(QString message); void failed(QString message);
private: 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);
IppPrinter* _printer; void awaitResult(CurlRequester& cr, QString callback);
std::unique_ptr<QThread> _thread;
QPointer<IppPrinter> _printer;
QUrl _url;
}; };
#endif // PRINTERWORKER_H #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> #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(); 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(QString ignoreSslErrorsPath READ ignoreSslErrorsPath CONSTANT)
Q_PROPERTY(bool ignoreSslErrorsDefault MEMBER _ignoreSslErrorsDefault 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: public:
static Settings* instance(); static Settings* instance();
bool ignoreSslErrors(); bool ignoreSslErrors();
bool debugLog();
bool allowExternalConnections();
private: private:
Settings(); Settings();
@ -25,10 +33,17 @@ private:
static Settings* m_Instance; static Settings* m_Instance;
MGConfItem _ignoreSslErrorsSetting; MGConfItem _ignoreSslErrorsSetting;
MGConfItem _debugLogSetting;
MGConfItem _allowExternalConnectionsSetting;
QString ignoreSslErrorsPath(); QString ignoreSslErrorsPath();
QString debugLogPath();
QString allowExternalConnectionsPath();
bool _ignoreSslErrorsDefault = true; bool _ignoreSslErrorsDefault = true;
bool _debugLogDefault = false;
bool _allowExternalConnectionsDefault = false;
}; };
#endif // SETTINGS_H #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