@@ -7,8 +7,8 @@ use adw::prelude::*;
7
7
use futures:: StreamExt ;
8
8
use gettextrs:: gettext;
9
9
use gtk4:: {
10
- gio, glib, glib:: clone, Builder , Button , CallbackAction , FileDialog , FileFilter , Label ,
11
- Shortcut , ShortcutController , ShortcutTrigger , ToggleButton ,
10
+ gio, glib, glib:: clone, graphene , gsk , Builder , Button , CallbackAction , FileDialog , FileFilter ,
11
+ Label , Shortcut , ShortcutController , ShortcutTrigger , ToggleButton ,
12
12
} ;
13
13
use num_traits:: ToPrimitive ;
14
14
use rnote_engine:: engine:: import:: { PdfImportPageSpacing , PdfImportPagesType } ;
@@ -111,6 +111,115 @@ pub(crate) async fn filedialog_import_file(appwindow: &RnAppWindow) {
111
111
}
112
112
}
113
113
114
+ /// Check for a pdf encryption and request a password if needed from the user
115
+ ///
116
+ /// Returns a password Option and a boolean weather the user canceled the file import or not
117
+ pub ( crate ) async fn pdf_encryption_check_and_dialog (
118
+ appwindow : & RnAppWindow ,
119
+ input_file : & gio:: File ,
120
+ ) -> ( Option < String > , bool ) {
121
+ let builder = Builder :: from_resource (
122
+ ( String :: from ( config:: APP_IDPATH ) + "ui/dialogs/import.ui" ) . as_str ( ) ,
123
+ ) ;
124
+
125
+ let dialog_import_pdf_password: adw:: AlertDialog =
126
+ builder. object ( "dialog_import_pdf_password" ) . unwrap ( ) ;
127
+ let pdf_password_entry: adw:: PasswordEntryRow = builder. object ( "pdf_password_entry" ) . unwrap ( ) ;
128
+ let pdf_password_entry_box: gtk4:: ListBox = builder. object ( "pdf_password_entry_box" ) . unwrap ( ) ;
129
+
130
+ let target = adw:: CallbackAnimationTarget :: new ( clone ! (
131
+ #[ weak]
132
+ pdf_password_entry_box,
133
+ move |value| {
134
+ let x = adw:: lerp( 0. , 40.0 , value) ;
135
+ let p = graphene:: Point :: new( x as f32 , 0. ) ;
136
+ let transform = gsk:: Transform :: new( ) . translate( & p) ;
137
+ pdf_password_entry_box. allocate(
138
+ pdf_password_entry_box. width( ) ,
139
+ pdf_password_entry_box. height( ) ,
140
+ -1 ,
141
+ Some ( transform) ,
142
+ ) ;
143
+ }
144
+ ) ) ;
145
+
146
+ let params = adw:: SpringParams :: new ( 0.2 , 0.5 , 500.0 ) ;
147
+
148
+ let animation = adw:: SpringAnimation :: builder ( )
149
+ . widget ( & pdf_password_entry_box)
150
+ . value_from ( 0.0 )
151
+ . value_to ( 0.0 )
152
+ . spring_params ( & params)
153
+ . target ( & target)
154
+ . initial_velocity ( 10.0 )
155
+ . epsilon ( 0.001 ) // If amplitude of oscillation < epsilon, animation stops
156
+ . clamp ( false )
157
+ . build ( ) ;
158
+
159
+ let ( tx, mut rx) = futures:: channel:: mpsc:: unbounded :: < ( Option < String > , bool ) > ( ) ;
160
+ let tx_cancel = tx. clone ( ) ;
161
+ let tx_unlock = tx. clone ( ) ;
162
+
163
+ dialog_import_pdf_password. connect_response (
164
+ Some ( "unlock" ) ,
165
+ clone ! (
166
+ #[ weak]
167
+ pdf_password_entry,
168
+ move |_, _| {
169
+ tx_unlock
170
+ . unbounded_send( ( Some ( pdf_password_entry. text( ) . to_string( ) ) , false ) )
171
+ . unwrap( ) ;
172
+ }
173
+ ) ,
174
+ ) ;
175
+
176
+ dialog_import_pdf_password. connect_response ( Some ( "cancel" ) , move |_, _| {
177
+ tx_cancel. unbounded_send ( ( None , true ) ) . unwrap ( ) ;
178
+ } ) ;
179
+
180
+ let file_name = input_file. basename ( ) . map_or_else (
181
+ || gettext ( "- no file name -" ) ,
182
+ |s| s. to_string_lossy ( ) . to_string ( ) ,
183
+ ) ;
184
+ let dialog_body = dialog_import_pdf_password. body ( ) ;
185
+ let dialog_body = file_name. clone ( ) + " " + & dialog_body;
186
+ dialog_import_pdf_password. set_body ( & dialog_body) ;
187
+
188
+ let mut password: Option < String > = None ;
189
+
190
+ loop {
191
+ match poppler:: Document :: from_gfile (
192
+ input_file,
193
+ password. as_deref ( ) ,
194
+ None :: < & gio:: Cancellable > ,
195
+ ) {
196
+ Ok ( _) => return ( password, false ) ,
197
+ Err ( e) => {
198
+ if e. matches ( poppler:: Error :: Encrypted ) {
199
+ dialog_import_pdf_password. present ( appwindow. root ( ) . as_ref ( ) ) ;
200
+ pdf_password_entry. grab_focus ( ) ;
201
+
202
+ match rx. next ( ) . await {
203
+ Some ( ( new_password, cancel) ) => {
204
+ password = new_password;
205
+ if cancel {
206
+ return ( None , true ) ;
207
+ }
208
+ }
209
+ None => {
210
+ return ( None , true ) ;
211
+ }
212
+ }
213
+ animation. play ( ) ;
214
+ pdf_password_entry. set_text ( "" ) ;
215
+ } else {
216
+ return ( None , true ) ;
217
+ }
218
+ }
219
+ } ;
220
+ }
221
+ }
222
+
114
223
/// Imports the file as Pdf with an import dialog.
115
224
///
116
225
/// Returns true when the file was imported, else false.
@@ -120,6 +229,11 @@ pub(crate) async fn dialog_import_pdf_w_prefs(
120
229
input_file : gio:: File ,
121
230
target_pos : Option < na:: Vector2 < f64 > > ,
122
231
) -> anyhow:: Result < bool > {
232
+ let ( password, cancel) = pdf_encryption_check_and_dialog ( appwindow, & input_file) . await ;
233
+ if cancel {
234
+ return Ok ( false ) ;
235
+ }
236
+
123
237
let builder = Builder :: from_resource (
124
238
( String :: from ( config:: APP_IDPATH ) + "ui/dialogs/import.ui" ) . as_str ( ) ,
125
239
) ;
@@ -274,7 +388,7 @@ pub(crate) async fn dialog_import_pdf_w_prefs(
274
388
) ) ;
275
389
276
390
if let Ok ( poppler_doc) =
277
- poppler:: Document :: from_gfile ( & input_file, None , None :: < & gio:: Cancellable > )
391
+ poppler:: Document :: from_gfile ( & input_file, password . as_deref ( ) , None :: < & gio:: Cancellable > )
278
392
{
279
393
let file_name = input_file. basename ( ) . map_or_else (
280
394
|| gettext ( "- no file name -" ) ,
@@ -346,12 +460,12 @@ pub(crate) async fn dialog_import_pdf_w_prefs(
346
460
}
347
461
) ) ;
348
462
349
- import_pdf_button_confirm. connect_clicked ( clone ! ( #[ weak] pdf_page_start_row, #[ weak] pdf_page_end_row, #[ weak] input_file, #[ weak] dialog, #[ weak] canvas , move |_| {
463
+ import_pdf_button_confirm. connect_clicked ( clone ! ( #[ weak] pdf_page_start_row, #[ weak] pdf_page_end_row, #[ weak] input_file, #[ weak] dialog, #[ weak] canvas, # [ strong ] password , move |_| {
350
464
dialog. close( ) ;
351
465
352
466
let inner_tx_confirm = tx_confirm. clone( ) ;
353
467
354
- glib:: spawn_future_local( clone!( #[ weak] pdf_page_start_row, #[ weak] pdf_page_end_row, #[ weak] input_file, #[ weak] canvas , async move {
468
+ glib:: spawn_future_local( clone!( #[ weak] pdf_page_start_row, #[ weak] pdf_page_end_row, #[ weak] input_file, #[ weak] canvas, # [ strong ] password , async move {
355
469
let page_range =
356
470
( pdf_page_start_row. value( ) as u32 - 1 ) ..pdf_page_end_row. value( ) as u32 ;
357
471
@@ -364,7 +478,7 @@ pub(crate) async fn dialog_import_pdf_w_prefs(
364
478
return ;
365
479
}
366
480
} ;
367
- if let Err ( e) = canvas. load_in_pdf_bytes( bytes. to_vec( ) , target_pos, Some ( page_range) ) . await {
481
+ if let Err ( e) = canvas. load_in_pdf_bytes( bytes. to_vec( ) , target_pos, Some ( page_range) , password ) . await {
368
482
if let Err ( e) = inner_tx_confirm. unbounded_send( Err ( e) ) {
369
483
error!( "Failed to load PDF, but failed to send signal through channel. Err: {e:?}" ) ;
370
484
}
0 commit comments