Merge lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64 into lp:ubuntu/precise/pdfshuffler

Proposed by Matthieu Baerts
Status: Merged
Merge reported by: Stefano Rivera
Merged at revision: not available
Proposed branch: lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64
Merge into: lp:ubuntu/precise/pdfshuffler
Diff against target: 5610 lines (+3230/-2150)
20 files modified
.pc/applied-patches (+1/-1)
.pc/fix-shebang/pdfshuffler (+0/-1031)
.pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler/pdfshuffler.py (+1057/-0)
.pc/install_and_look_for_UI_file_in_right_dirs.patch/setup.py (+66/-0)
TODO (+0/-2)
bin/pdfshuffler (+33/-0)
data/pdfshuffler.ui (+359/-0)
debian/changelog (+11/-0)
debian/patches/fix-shebang (+0/-12)
debian/patches/install_and_look_for_UI_file_in_right_dirs.patch (+31/-0)
debian/patches/series (+1/-1)
doc/pdfshuffler.1 (+2/-2)
pdfshuffler (+0/-1031)
pdfshuffler/cairorendering.py (+168/-0)
pdfshuffler/pdfshuffler.py (+1057/-0)
po/genpot.sh (+2/-1)
po/ja.po (+156/-0)
po/pdfshuffler.pot (+118/-60)
po/zh_CN.po (+156/-0)
setup.py (+12/-9)
To merge this branch: bzr merge lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64
Reviewer Review Type Date Requested Status
Ubuntu Sponsors Pending
Review via email: mp+92648@code.launchpad.net

Description of the change

Hello,

Is it possible to upload this new version because it fixes this annoying bug: PDFShuffler displays only black squares
 - http://sourceforge.net/tracker/?func=detail&aid=3293749&group_id=235357&atid=1095819
 - https://bugzilla.redhat.com/show_bug.cgi?id=705153

This version has been tested and it is available on this ppa:matttbe/ppa
This branch should be ready to be pushed on lp:ubuntu/pdfshuffler (except the "UNRELEASED" version in debian/changelog)

Please upload this package before the FF :)

Thank you for your help!

To post a comment you must log in.
Revision history for this message
Stefano Rivera (stefanor) wrote :

Eep, this breaks my bzr
bzr: ERROR: exceptions.AttributeError: 'DirStateRevisionTree' object has no attribute 'last_revision'

Revision history for this message
Matthieu Baerts (matttbe) wrote :

It's strange! I simply used 'bzr merge-upstream' with the tarball...
And if I want to give you a debdiff, I have this error: File /tmp/ThBQcppeQT/pdfshuffler-0.5.1/pdfshuffler is a regular file while file /tmp/rkd3EH77bx/pdfshuffler-0.5.99~svn64/pdfshuffler is a directory...

What can I do?

Revision history for this message
Matthieu Baerts (matttbe) wrote :

This is strange... These two commands work fine:
    bzr branch lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64
    bzr bd lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64
But not this one:
    bzr merge lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64

Revision history for this message
Stefano Rivera (stefanor) wrote :

Yeah, it's LP: #923688. I worked around it...

Revision history for this message
Matthieu Baerts (matttbe) wrote :

Oh ok, it's a known bug!
Thank you for your help :)

Revision history for this message
Stefano Rivera (stefanor) wrote :

Slightly modified the changelog and uploaded.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.pc/applied-patches'
2--- .pc/applied-patches 2010-09-15 23:33:31 +0000
3+++ .pc/applied-patches 2012-02-12 00:35:23 +0000
4@@ -1,1 +1,1 @@
5-fix-shebang
6+install_and_look_for_UI_file_in_right_dirs.patch
7
8=== removed directory '.pc/fix-shebang'
9=== removed file '.pc/fix-shebang/pdfshuffler'
10--- .pc/fix-shebang/pdfshuffler 2011-02-15 00:43:28 +0000
11+++ .pc/fix-shebang/pdfshuffler 1970-01-01 00:00:00 +0000
12@@ -1,1031 +0,0 @@
13-#!/usr/bin/env python
14-# -*- coding: utf-8 -*-
15-
16-"""
17- --------------------------------------------------------------------------
18-
19- PDF-Shuffler 0.5.1 - pyGTK PDF Merging, Rearranging, and Splitting
20- Copyright (C) 2008-2010 Konstantinos Poulios
21- <https://sourceforge.net/projects/pdfshuffler>
22-
23- --------------------------------------------------------------------------
24-
25- This program is free software; you can redistribute it and/or modify
26- it under the terms of the GNU General Public License as published by
27- the Free Software Foundation; either version 3 of the License, or
28- (at your option) any later version.
29-
30- This program is distributed in the hope that it will be useful,
31- but WITHOUT ANY WARRANTY; without even the implied warranty of
32- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33- GNU General Public License for more details.
34-
35- You should have received a copy of the GNU General Public License along
36- with this program; if not, write to the Free Software Foundation, Inc.,
37- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
38-
39- --------------------------------------------------------------------------
40-"""
41-
42-import os
43-import shutil #needed for file operations like whole directory deletion
44-import sys #needed for proccessing of command line args
45-import urllib #needed to parse filename information passed by DnD
46-import threading
47-import tempfile
48-from copy import copy
49-
50-import locale #for multilanguage support
51-import gettext
52-gettext.install('pdfshuffler', unicode=1)
53-
54-try:
55- import pygtk
56- pygtk.require('2.0')
57- import gtk
58- assert gtk.gtk_version >= (2, 10, 0)
59- assert gtk.pygtk_version >= (2, 10, 0)
60-except AssertionError:
61- print('You do not have the required versions of GTK+ and/or PyGTK ' +
62- 'installed.\n\n' +
63- 'Installed GTK+ version is ' +
64- '.'.join([str(n) for n in gtk.gtk_version]) + '\n' +
65- 'Required GTK+ version is 2.10.0 or higher\n\n'
66- 'Installed PyGTK version is ' +
67- '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' +
68- 'Required PyGTK version is 2.10.0 or higher')
69- sys.exit(1)
70-except:
71- print('PyGTK version 2.10.0 or higher is required to run this program.')
72- print('No version of PyGTK was found on your system.')
73- sys.exit(1)
74-
75-gtk.gdk.threads_init()
76-import gobject #to use custom signals
77-import pango #to adjust the text alignment in CellRendererText
78-import gio #to inquire mime types information
79-
80-import poppler #for the rendering of pdf pages
81-from pyPdf import PdfFileWriter, PdfFileReader
82-
83-class PDFshuffler:
84- # =======================================================
85- # All the preferences are stored here.
86- # =======================================================
87- prefs = {
88- 'window width': min (700, gtk.gdk.screen_get_default().get_width() / 2 ),
89- 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50 ),
90- 'window x': 0,
91- 'window y': 0,
92- 'initial thumbnail size': 530,
93- 'initial zoom scale': 0.25,
94- }
95-
96- MODEL_ROW_INTERN = 1001
97- MODEL_ROW_EXTERN = 1002
98- TEXT_URI_LIST = 1003
99- MODEL_ROW_MOTION = 1004
100- TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN),
101- ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN),
102- ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)]
103- TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST),
104- ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)]
105-
106- def __init__(self):
107- # Create the temporary directory
108- self.tmp_dir = tempfile.mkdtemp("pdfshuffler")
109- os.chmod(self.tmp_dir, 0700)
110-
111- pixmap = os.path.join(sys.prefix,'share','pixmaps','pdfshuffler.png')
112- try:
113- gtk.window_set_default_icon_from_file(pixmap)
114- except:
115- print(_('File %s does not exist') % pixmap)
116-
117- # Create the main window, and attach delete_event signal to terminating
118- # the application
119- self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
120- self.window.set_title('PDF-Shuffler')
121- self.window.set_border_width(0)
122- self.window.move(self.prefs['window x'], self.prefs['window y'])
123- self.window.set_size_request(self.prefs['window width'],
124- self.prefs['window height'])
125- self.window.connect('delete_event', self.close_application)
126- self.window.show()
127-
128- # Create a vbox to hold the thumnails-container
129- vbox = gtk.VBox()
130- self.window.add(vbox)
131-
132- # Create a scrolled window to hold the thumbnails-container
133- self.sw = gtk.ScrolledWindow()
134- self.sw.set_border_width(0)
135- self.sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
136- self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
137- gtk.DEST_DEFAULT_HIGHLIGHT |
138- gtk.DEST_DEFAULT_DROP |
139- gtk.DEST_DEFAULT_MOTION,
140- self.TARGETS_SW,
141- gtk.gdk.ACTION_COPY |
142- gtk.gdk.ACTION_MOVE )
143- self.sw.connect('drag_data_received', self.sw_dnd_received_data)
144- self.sw.connect('button_press_event', self.sw_button_press_event)
145- vbox.pack_start(self.sw)
146-
147- # Create an alignment to keep the thumbnails center-aligned
148- align = gtk.Alignment(0.5, 0.5, 0, 0)
149- self.sw.add_with_viewport(align)
150-
151- # Create ListStore model and IconView
152- self.model = gtk.ListStore(str, # 0.Text descriptor
153- gtk.gdk.Pixbuf, # 1.Thumbnail image
154- int, # 2.Document number
155- int, # 3.Page number
156- int, # 4.Thumbnail width
157- str, # 5.Document filename
158- bool, # 6.Rendered
159- int, # 7.Rotation angle
160- float, # 8.Crop left
161- float, # 9.Crop right
162- float, # 10.Crop top
163- float) # 11.Crop bottom
164-
165- self.zoom_scale = self.prefs['initial zoom scale']
166- self.iv_col_width = self.prefs['initial thumbnail size']
167-
168- self.iconview = gtk.IconView(self.model)
169- self.iconview.set_item_width(self.iv_col_width + 12)
170-
171- self.iconview.set_pixbuf_column(1)
172-# self.cellpb = gtk.CellRendererPixbuf()
173-# self.cellpb.set_property('follow-state', True)
174-# self.iconview.pack_start(self.cellpb, False)
175-# self.iconview.set_attributes(self.cellpb, pixbuf=1)
176-
177-# self.iconview.set_text_column(0)
178- self.celltxt = gtk.CellRendererText()
179- self.celltxt.set_property('width', self.iv_col_width)
180- self.celltxt.set_property('wrap-width', self.iv_col_width)
181- self.celltxt.set_property('alignment', pango.ALIGN_CENTER)
182- self.iconview.pack_start(self.celltxt, False)
183- self.iconview.set_attributes(self.celltxt, text=0)
184-
185- self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE)
186- self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
187- self.TARGETS_IV,
188- gtk.gdk.ACTION_COPY |
189- gtk.gdk.ACTION_MOVE )
190- self.iconview.enable_model_drag_dest(self.TARGETS_IV,
191- gtk.gdk.ACTION_DEFAULT)
192- self.iconview.connect('drag_begin', self.iv_drag_begin)
193- self.iconview.connect('drag_data_get', self.iv_dnd_get_data)
194- self.iconview.connect('drag_data_received', self.iv_dnd_received_data)
195- self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete)
196- self.iconview.connect('drag_motion', self.iv_dnd_motion)
197- self.iconview.connect('drag_leave', self.iv_dnd_leave_end)
198- self.iconview.connect('drag_end', self.iv_dnd_leave_end)
199- self.iconview.connect('button_press_event', self.iv_button_press_event)
200- self.iv_auto_scroll_direction = 0
201-
202- style = self.iconview.get_style().copy()
203- style_sw = self.sw.get_style()
204- for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE):
205- style.base[state] = style_sw.bg[gtk.STATE_NORMAL]
206- self.iconview.set_style(style)
207-
208- align.add(self.iconview)
209-
210- # Create a horizontal box to hold the buttons
211- hbox = gtk.HBox()
212- vbox.pack_start(hbox, expand=False, fill=False)
213-
214- # Create buttons
215- self.button_exit = gtk.Button(stock=gtk.STOCK_QUIT)
216- self.button_exit.connect('clicked', self.close_application)
217- hbox.pack_start(self.button_exit, expand=True, fill=True, padding=20)
218-
219- self.button_del = gtk.Button(_('Delete Page(s)'))
220- image = gtk.Image()
221- image.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_BUTTON)
222- self.button_del.set_image(image)
223- self.button_del.connect('clicked', self.clear_selected)
224- hbox.pack_start(self.button_del, expand=True, fill=True, padding=20)
225-
226- self.button_import = gtk.Button(_('Import pdf'))
227- image = gtk.Image()
228- image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON)
229- self.button_import.set_image(image)
230- self.button_import.connect('clicked', self.on_action_add_doc_activate)
231- hbox.pack_start(self.button_import, expand=True, fill=True, padding=20)
232-
233- self.button_export = gtk.Button(_('Export pdf'))
234- image = gtk.Image()
235- image.set_from_stock(gtk.STOCK_SAVE_AS, gtk.ICON_SIZE_BUTTON)
236- self.button_export.set_image(image)
237- self.button_export.connect('clicked', self.choose_export_pdf_name)
238- hbox.pack_start(self.button_export, expand=True, fill=True, padding=20)
239-
240- self.button_export = gtk.Button(_('About'))
241- image = gtk.Image()
242- image.set_from_stock(gtk.STOCK_ABOUT, gtk.ICON_SIZE_BUTTON)
243- self.button_export.set_image(image)
244- self.button_export.connect('clicked', self.about_dialog)
245- hbox.pack_start(self.button_export, expand=True, fill=True, padding=20)
246-
247- # Define window callback function and show window
248- self.window.connect('size_allocate', self.on_window_size_request) # resize
249- self.window.connect('key_press_event', self.on_keypress_event ) # keypress
250- self.window.show_all()
251-
252- #Creating the popup menu
253- self.popup = gtk.Menu()
254- popup_rotate_right = gtk.MenuItem(_('Rotate Page(s) Clockwise'))
255- popup_rotate_left = gtk.MenuItem(_('Rotate Page(s) Counterclockwise'))
256- popup_crop = gtk.MenuItem(_('Crop Page(s)'))
257- popup_delete = gtk.MenuItem(_('Delete Page(s)'))
258- popup_rotate_right.connect('activate', self.rotate_page_right)
259- popup_rotate_left.connect('activate', self.rotate_page_left)
260- popup_crop.connect('activate', self.crop_page_dialog)
261- popup_delete.connect('activate', self.clear_selected)
262- popup_rotate_right.show()
263- popup_rotate_left.show()
264- popup_crop.show()
265- popup_delete.show()
266- self.popup.append(popup_rotate_right)
267- self.popup.append(popup_rotate_left)
268- self.popup.append(popup_crop)
269- self.popup.append(popup_delete)
270-
271- # Initializing variables
272- self.export_directory = os.getenv('HOME')
273- self.import_directory = os.getenv('HOME')
274- self.nfile = 0
275- self.iv_auto_scroll_timer = None
276- self.pdfqueue = []
277-
278- gobject.type_register(PDF_Renderer)
279- gobject.signal_new('reset_iv_width', PDF_Renderer,
280- gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
281- self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue,
282- self.zoom_scale, self.iv_col_width)
283- self.rendering_thread.connect('reset_iv_width', self.reset_iv_width)
284- self.rendering_thread.start()
285-
286- # Importing documents passed as command line arguments
287- for filename in sys.argv[1:]:
288- self.add_pdf_pages(filename)
289-
290- # =======================================================
291- def render(self):
292- if self.rendering_thread.paused:
293- self.rendering_thread.paused = False
294- self.rendering_thread.evnt.set()
295- self.rendering_thread.evnt.clear()
296-
297- # =======================================================
298- def on_window_size_request(self, window, event):
299- """Main Window resize - workaround for autosetting of
300- iconview cols no."""
301-
302- #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152
303- col_num = 9 * window.get_size()[0] / (10 * (self.iv_col_width + 12))
304- self.iconview.set_columns(col_num)
305-
306- # =======================================================
307- def reset_iv_width(self, renderer=None):
308- """Reconfigures the width of the iconview columns"""
309-
310- max_w = max(row[4] for row in self.model)
311- if max_w != self.iv_col_width:
312- self.iv_col_width = max_w
313- self.celltxt.set_property('width', self.iv_col_width)
314- self.celltxt.set_property('wrap-width', self.iv_col_width)
315- self.iconview.set_item_width(self.iv_col_width + 12) #-1)
316- self.on_window_size_request(self.window, None)
317-
318- # =======================================================
319- def on_keypress_event(self, widget, event):
320- """Keypress events in Main Window"""
321-
322- #keyname = gtk.gdk.keyval_name(event.keyval)
323- if event.keyval == 65535: # Delete keystroke
324- self.clear_selected()
325-
326- # =======================================================
327- def close_application(self, widget, event=None, data=None):
328- """Termination"""
329-
330- #gtk.gdk.threads_leave()
331- self.rendering_thread.quit = True
332- #gtk.gdk.threads_enter()
333- if self.rendering_thread.paused == True:
334- self.rendering_thread.evnt.set()
335- self.rendering_thread.evnt.clear()
336- if os.path.isdir(self.tmp_dir):
337- shutil.rmtree(self.tmp_dir)
338- if gtk.main_level():
339- gtk.main_quit()
340- else:
341- sys.exit(0)
342- return False
343-
344- # =======================================================
345- def add_pdf_pages(self, filename,
346- firstpage=None, lastpage=None,
347- angle=0, crop=[0.,0.,0.,0.] ):
348- """Add pages of a pdf document to the model"""
349-
350- res = False
351- # Check if the document has already been loaded
352- pdfdoc = None
353- for it_pdfdoc in self.pdfqueue:
354- if os.path.isfile(it_pdfdoc.filename) and \
355- os.path.samefile(filename, it_pdfdoc.filename) and \
356- os.path.getmtime(filename) is it_pdfdoc.mtime:
357- pdfdoc = it_pdfdoc
358- break
359-
360- if not pdfdoc:
361- pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir)
362- self.import_directory = os.path.split(filename)[0]
363- self.export_directory = self.import_directory
364- if pdfdoc.nfile != 0 and pdfdoc != []:
365- self.nfile = pdfdoc.nfile
366- self.pdfqueue.append(pdfdoc)
367- else:
368- return res
369-
370- n_start = 1
371- n_end = pdfdoc.npage
372- if firstpage:
373- n_start = min(n_end, max(1, firstpage))
374- if lastpage:
375- n_end = max(n_start, min(n_end, lastpage))
376-
377- for npage in range(n_start, n_end + 1):
378- descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)])
379- width = self.iv_col_width
380- thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
381- 8, width, width)
382- self.model.append((descriptor, # 0
383- thumbnail, # 1
384- pdfdoc.nfile, # 2
385- npage, # 3
386- width, # 4
387- pdfdoc.filename, # 5
388- False, # 6
389- angle, # 7
390- crop[0],crop[1], # 8-9
391- crop[2],crop[3] )) # 10-11
392- res = True
393-
394- if res:
395- self.render()
396- return res
397-
398- # =======================================================
399- def choose_export_pdf_name(self, widget=None):
400- """Handles choosing a name for exporting """
401-
402- chooser = gtk.FileChooserDialog(title=_('Export ...'),
403- action=gtk.FILE_CHOOSER_ACTION_SAVE,
404- buttons=(gtk.STOCK_CANCEL,
405- gtk.RESPONSE_CANCEL,
406- gtk.STOCK_SAVE,
407- gtk.RESPONSE_OK))
408- chooser.set_do_overwrite_confirmation(True)
409- chooser.set_current_folder(self.export_directory)
410- filter_pdf = gtk.FileFilter()
411- filter_pdf.set_name(_('PDF files'))
412- filter_pdf.add_mime_type('application/pdf')
413- chooser.add_filter(filter_pdf)
414-
415- filter_all = gtk.FileFilter()
416- filter_all.set_name(_('All files'))
417- filter_all.add_pattern('*')
418- chooser.add_filter(filter_all)
419-
420- while True:
421- response = chooser.run()
422- if response == gtk.RESPONSE_OK:
423- file_out = chooser.get_filename()
424- (path, shortname) = os.path.split(file_out)
425- (shortname, ext) = os.path.splitext(shortname)
426- if ext.lower() != '.pdf':
427- file_out = file_out + '.pdf'
428- try:
429- self.export_to_file(file_out)
430- self.export_directory = path
431- except IOError:
432- error_msg_win = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
433- type=gtk.MESSAGE_ERROR,
434- message_format=_("Error writing file: %s") % file_out,
435- buttons=gtk.BUTTONS_OK)
436- response = error_msg_win.run()
437- if response == gtk.RESPONSE_OK:
438- error_msg_win.destroy()
439- continue
440- break
441- chooser.destroy()
442-
443- # =======================================================
444- def export_to_file(self, file_out):
445- """Export to file"""
446-
447- pdf_output = PdfFileWriter()
448- pdf_input = []
449- for pdfdoc in self.pdfqueue:
450- pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb'))
451- if pdfdoc_inp.getIsEncrypted():
452- if (pdfdoc_inp.decrypt('')!=1): # Workaround for lp:#355479
453- print(_('File %s is encrypted.') % pdfdoc.filename)
454- print(_('Support for such files has not been implemented yet.'))
455- print(_('File export failed.'))
456- return
457- #FIXME
458- #else
459- # ask for password and decrypt file
460- pdf_input.append(pdfdoc_inp)
461-
462- for row in self.model:
463- # add pages from input to output document
464- nfile = row[2]
465- npage = row[3]
466- current_page = copy(pdf_input[nfile-1].getPage(npage-1))
467- angle = row[7]
468- angle0 = current_page.get("/Rotate",0)
469- crop = [row[8],row[9],row[10],row[11]]
470- if angle is not 0:
471- current_page.rotateClockwise(angle)
472- if crop != [0.,0.,0.,0.]:
473- rotate_times = ( ( ( angle + angle0 ) % 360 + 45 ) / 90 ) % 4
474- crop_init = crop
475- if rotate_times is not 0:
476- perm = [0,2,1,3]
477- for it in range(rotate_times):
478- perm.append(perm.pop(0))
479- perm.insert(1,perm.pop(2))
480- crop = [crop_init[perm[side]] for side in range(4)]
481- #(x1, y1) = current_page.cropBox.lowerLeft
482- #(x2, y2) = current_page.cropBox.upperRight
483- (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft]
484- (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight]
485- x1_new = int( x1 + (x2-x1) * crop[0] )
486- x2_new = int( x2 - (x2-x1) * crop[1] )
487- y1_new = int( y1 + (y2-y1) * crop[3] )
488- y2_new = int( y2 - (y2-y1) * crop[2] )
489- #current_page.cropBox.lowerLeft = (x1_new, y1_new)
490- #current_page.cropBox.upperRight = (x2_new, y2_new)
491- current_page.mediaBox.lowerLeft = (x1_new, y1_new)
492- current_page.mediaBox.upperRight = (x2_new, y2_new)
493-
494- pdf_output.addPage(current_page)
495-
496- # finally, write "output" to document-output.pdf
497- print(_('exporting to:'), file_out)
498- pdf_output.write(file(file_out, 'wb'))
499-
500- # =======================================================
501- def on_action_add_doc_activate(self, widget, data=None):
502- """Import doc"""
503-
504- chooser = gtk.FileChooserDialog(title=_('Import...'),
505- action=gtk.FILE_CHOOSER_ACTION_OPEN,
506- buttons=(gtk.STOCK_CANCEL,
507- gtk.RESPONSE_CANCEL,
508- gtk.STOCK_OPEN,
509- gtk.RESPONSE_OK))
510- chooser.set_current_folder(self.import_directory)
511- chooser.set_select_multiple(True)
512-
513- filter_all = gtk.FileFilter()
514- filter_all.set_name(_('All files'))
515- filter_all.add_pattern('*')
516- chooser.add_filter(filter_all)
517-
518- filter_pdf = gtk.FileFilter()
519- filter_pdf.set_name(_('PDF files'))
520- filter_pdf.add_mime_type('application/pdf')
521- chooser.add_filter(filter_pdf)
522- chooser.set_filter(filter_pdf)
523-
524- response = chooser.run()
525- if response == gtk.RESPONSE_OK:
526- for filename in chooser.get_filenames():
527- if os.path.isfile(filename):
528- # FIXME
529- f = gio.File(filename)
530- f_info = f.query_info('standard::content-type')
531- mime_type = f_info.get_content_type()
532- if mime_type == 'application/pdf':
533- self.add_pdf_pages(filename)
534- elif mime_type[:34] == 'application/vnd.oasis.opendocument':
535- print(_('OpenDocument not supported yet!'))
536- elif mime_type[:5] == 'image':
537- print(_('Image file not supported yet!'))
538- else:
539- print(_('File type not supported!'))
540- else:
541- print(_('File %s does not exist') % filename)
542- elif response == gtk.RESPONSE_CANCEL:
543- print(_('Closed, no files selected'))
544- chooser.destroy()
545-
546- # =======================================================
547- def clear_selected(self, button=None):
548- """Removes the selected Element in the IconView"""
549-
550- model = self.iconview.get_model()
551- selection = self.iconview.get_selected_items()
552- if selection:
553- selection.sort(reverse=True)
554- for path in selection:
555- iter = model.get_iter(path)
556- model.remove(iter)
557- path = selection[-1]
558- self.iconview.select_path(path)
559- if not self.iconview.path_is_selected(path):
560- if len(model) > 0: # select the last row
561- row = model[-1]
562- path = row.path
563- self.iconview.select_path(path)
564- self.iconview.grab_focus()
565-
566- # =======================================================
567- def iv_drag_begin(self, iconview, context):
568- """Sets custom icon on drag begin for multiple items selected"""
569-
570- if len(iconview.get_selected_items()) > 1:
571- iconview.stop_emission('drag_begin')
572- context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0)
573-
574- # =======================================================
575- def iv_dnd_get_data(self, iconview, context,
576- selection_data, target_id, etime):
577- """Handles requests for data by drag and drop in iconview"""
578-
579- model = iconview.get_model()
580- selection = self.iconview.get_selected_items()
581- selection.sort(key=lambda x: x[0])
582- data = []
583- for path in selection:
584- if selection_data.target == 'MODEL_ROW_INTERN':
585- data.append( str(path[0]) )
586- elif selection_data.target == 'MODEL_ROW_EXTERN':
587- iter = model.get_iter(path)
588- nfile, npage, angle = model.get(iter, 2, 3, 7)
589- crop = model.get(iter, 8, 9, 10, 11)
590- pdfdoc = self.pdfqueue[nfile - 1]
591- data.append('\n'.join([pdfdoc.filename,
592- str(npage),
593- str(angle) ] +
594- [str(side) for side in crop] ) )
595- if data:
596- data = '\n;\n'.join(data)
597- selection_data.set(selection_data.target, 8, data)
598-
599- # =======================================================
600- def iv_dnd_received_data(self, iconview, context, x, y,
601- selection_data, target_id, etime):
602- """Handles received data by drag and drop in iconview"""
603-
604- model = iconview.get_model()
605- data = selection_data.data
606- if data:
607- data = data.split('\n;\n')
608- drop_info = iconview.get_dest_item_at_pos(x, y)
609- iter_to = None
610- if drop_info:
611- path, position = drop_info
612- ref_to = gtk.TreeRowReference(model,path)
613- else:
614- position = gtk.ICON_VIEW_DROP_RIGHT
615- if len(model) > 0: #find the iterator of the last row
616- row = model[-1]
617- path = row.path
618- ref_to = gtk.TreeRowReference(model,path)
619- if ref_to:
620- before = ( position == gtk.ICON_VIEW_DROP_LEFT
621- or position == gtk.ICON_VIEW_DROP_ABOVE)
622- #if target_id == self.MODEL_ROW_INTERN:
623- if selection_data.target == 'MODEL_ROW_INTERN':
624- if before:
625- data.sort(key=int)
626- else:
627- data.sort(key=int,reverse=True)
628- ref_from_list = [gtk.TreeRowReference(model,path)
629- for path in data]
630- for ref_from in ref_from_list:
631- path = ref_to.get_path()
632- iter_to = model.get_iter(path)
633- path = ref_from.get_path()
634- iter_from = model.get_iter(path)
635- row = model[iter_from]
636- if before:
637- model.insert_before(iter_to, row)
638- else:
639- model.insert_after(iter_to, row)
640- if context.action == gtk.gdk.ACTION_MOVE:
641- for ref_from in ref_from_list:
642- path = ref_from.get_path()
643- iter_from = model.get_iter(path)
644- model.remove(iter_from)
645-
646- #elif target_id == self.MODEL_ROW_EXTERN:
647- elif selection_data.target == 'MODEL_ROW_EXTERN':
648- if not before:
649- data.reverse()
650- while data:
651- tmp = data.pop(0).split('\n')
652- filename = tmp[0]
653- npage, angle = [int(k) for k in tmp[1:3]]
654- crop = [float(side) for side in tmp[3:7]]
655- if self.add_pdf_pages(filename, npage, npage,
656- angle, crop ):
657- if len(model) > 0:
658- path = ref_to.get_path()
659- iter_to = model.get_iter(path)
660- row = model[-1] #the last row
661- path = row.path
662- iter_from = model.get_iter(path)
663- if before:
664- model.move_before(iter_from, iter_to)
665- else:
666- model.move_after(iter_from, iter_to)
667- if context.action == gtk.gdk.ACTION_MOVE:
668- context.finish(True, True, etime)
669-
670- # =======================================================
671- def iv_dnd_data_delete(self, widget, context):
672- """Deletes dnd items after a successful move operation"""
673-
674- model = self.iconview.get_model()
675- selection = self.iconview.get_selected_items()
676- ref_del_list = [gtk.TreeRowReference(model,path) for path in selection]
677- for ref_del in ref_del_list:
678- path = ref_del.get_path()
679- iter = model.get_iter(path)
680- model.remove(iter)
681-
682- # =======================================================
683- def iv_dnd_motion(self, iconview, context, x, y, etime):
684- """Handles the drag-motion signal in order to auto-scroll the view"""
685-
686- autoscroll_area = 40
687- sw_vadj = self.sw.get_vadjustment()
688- sw_height = self.sw.get_allocation().height
689- if y -sw_vadj.get_value() < autoscroll_area:
690- if not self.iv_auto_scroll_timer:
691- self.iv_auto_scroll_direction = gtk.DIR_UP
692- self.iv_auto_scroll_timer = gobject.timeout_add(150,
693- self.iv_auto_scroll)
694- elif y -sw_vadj.get_value() > sw_height - autoscroll_area:
695- if not self.iv_auto_scroll_timer:
696- self.iv_auto_scroll_direction = gtk.DIR_DOWN
697- self.iv_auto_scroll_timer = gobject.timeout_add(150,
698- self.iv_auto_scroll)
699- elif self.iv_auto_scroll_timer:
700- gobject.source_remove(self.iv_auto_scroll_timer)
701- self.iv_auto_scroll_timer = None
702-
703- # =======================================================
704- def iv_dnd_leave_end(self, widget, context, ignored=None):
705- """Ends the auto-scroll during DND"""
706-
707- if self.iv_auto_scroll_timer:
708- gobject.source_remove(self.iv_auto_scroll_timer)
709- self.iv_auto_scroll_timer = None
710-
711- # =======================================================
712- def iv_auto_scroll(self):
713- """Timeout routine for auto-scroll"""
714-
715- sw_vadj = self.sw.get_vadjustment()
716- sw_vpos = sw_vadj.get_value()
717- if self.iv_auto_scroll_direction == gtk.DIR_UP:
718- sw_vpos -= sw_vadj.step_increment
719- sw_vadj.set_value( max(sw_vpos, sw_vadj.lower) )
720- elif self.iv_auto_scroll_direction == gtk.DIR_DOWN:
721- sw_vpos += sw_vadj.step_increment
722- sw_vadj.set_value( min(sw_vpos, sw_vadj.upper - sw_vadj.page_size) )
723- return True #call me again
724-
725- # =======================================================
726- def iv_button_press_event(self, iconview, event):
727- """Manages mouse clicks on the iconview"""
728-
729- if event.button == 3:
730- x = int(event.x)
731- y = int(event.y)
732- time = event.time
733- path = iconview.get_path_at_pos(x, y)
734- selection = iconview.get_selected_items()
735- if path:
736- if path not in selection:
737- iconview.unselect_all()
738- iconview.select_path(path)
739- iconview.grab_focus()
740- self.popup.popup( None, None, None, event.button, time)
741- return 1
742-
743- # =======================================================
744- def sw_dnd_received_data(self, scrolledwindow, context, x, y,
745- selection_data, target_id, etime):
746- """Handles received data by drag and drop in scrolledwindow"""
747-
748- data = selection_data.data
749- if target_id == self.MODEL_ROW_EXTERN:
750- self.model
751- if data:
752- data = data.split('\n;\n')
753- while data:
754- tmp = data.pop(0).split('\n')
755- filename = tmp[0]
756- npage, angle = [int(k) for k in tmp[1:3]]
757- crop = [float(side) for side in tmp[3:7]]
758- if self.add_pdf_pages(filename, npage, npage, angle, crop):
759- if context.action == gtk.gdk.ACTION_MOVE:
760- context.finish(True, True, etime)
761- elif target_id == self.TEXT_URI_LIST:
762- uri = data.strip()
763- uri_splitted = uri.split() # we may have more than one file dropped
764- for uri in uri_splitted:
765- filename = self.get_file_path_from_dnd_dropped_uri(uri)
766- if os.path.isfile(filename): # is it file?
767- self.add_pdf_pages(filename)
768-
769- # =======================================================
770- def sw_button_press_event(self, scrolledwindow, event):
771- """Unselects all items in iconview on mouse click in scrolledwindow"""
772-
773- if event.button == 1:
774- self.iconview.unselect_all()
775-
776- # =======================================================
777- def get_file_path_from_dnd_dropped_uri(self, uri):
778- """Extracts the path from an uri"""
779-
780- path = urllib.url2pathname(uri) # escape special chars
781- path = path.strip('\r\n\x00') # remove \r\n and NULL
782-
783- # get the path to file
784- if path.startswith('file:\\\\\\'): # windows
785- path = path[8:] # 8 is len('file:///')
786- elif path.startswith('file://'): # nautilus, rox
787- path = path[7:] # 7 is len('file://')
788- elif path.startswith('file:'): # xffm
789- path = path[5:] # 5 is len('file:')
790- return path
791-
792- # =======================================================
793- def rotate_page_right(self, widget, data=None):
794- self.rotate_page(90)
795-
796- def rotate_page_left(self, widget, data=None):
797- self.rotate_page(-90)
798-
799- def rotate_page(self, angle):
800- """Rotates the selected page in the IconView"""
801-
802- model = self.iconview.get_model()
803- selection = self.iconview.get_selected_items()
804- for path in selection:
805- iter = model.get_iter(path)
806- nfile = model.get_value(iter, 2)
807- npage = model.get_value(iter, 3)
808-
809- rotate_times = ( ( (-angle) % 360 + 45 ) / 90 ) % 4
810- crop = [0.,0.,0.,0.]
811- if rotate_times is not 0:
812- perm = [0,2,1,3]
813- for it in range(rotate_times):
814- perm.append(perm.pop(0))
815- perm.insert(1,perm.pop(2))
816- crop = [model.get_value(iter, 8 + perm[side]) for side in range(4)]
817- for side in range(4):
818- model.set_value(iter, 8 + side, crop[side])
819-
820- new_angle = model.get_value(iter, 7) + angle
821- model.set_value(iter, 7, new_angle)
822- model.set_value(iter, 6, False) #rendering request
823-
824- self.render()
825-
826- # =======================================================
827- def crop_page_dialog(self, widget):
828- """Opens a dialog box to define margins for page cropping"""
829-
830- sides = ('L', 'R', 'T', 'B')
831- side_names = {'L':_('Left'), 'R':_('Right'),
832- 'T':_('Top'), 'B':_('Bottom') }
833- opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' }
834-
835- def set_crop_value(spinbutton, side):
836- opp_side = opposite_sides[side]
837- pos = sides.index(opp_side)
838- adj = spin_list[pos].get_adjustment()
839- adj.set_upper(99.0 - spinbutton.get_value())
840-
841- model = self.iconview.get_model()
842- selection = self.iconview.get_selected_items()
843-
844- crop = [0.,0.,0.,0.]
845- if selection:
846- path = selection[0]
847- pos = model.get_iter(path)
848- crop = [model.get_value(pos, 8 + side) for side in range(4)]
849-
850- dialog = gtk.Dialog(title=(_('Crop Selected Page(s)')),
851- parent=self.window,
852- flags=gtk.DIALOG_MODAL,
853- buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
854- gtk.STOCK_OK, gtk.RESPONSE_OK))
855- dialog.set_size_request(340, 250)
856- dialog.set_default_response(gtk.RESPONSE_OK)
857-
858- frame = gtk.Frame(_('Crop Margins'))
859- dialog.vbox.pack_start(frame, False, False, 20)
860-
861- vbox = gtk.VBox(False, 0)
862- frame.add(vbox)
863-
864- spin_list = []
865- units = 2 * [_('% of width')] + 2 * [_('% of height')]
866- for side in sides:
867- hbox = gtk.HBox(True, 0)
868- vbox.pack_start(hbox, False, False, 5)
869-
870- label = gtk.Label(side_names[side])
871- label.set_alignment(0, 0.0)
872- hbox.pack_start(label, True, True, 20)
873-
874- adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0)
875- spin = gtk.SpinButton(adj, 0, 1)
876- spin.set_activates_default(True)
877- spin.connect('value-changed', set_crop_value, side)
878- spin_list.append(spin)
879- hbox.pack_start(spin, False, False, 30)
880-
881- label = gtk.Label(units.pop(0))
882- label.set_alignment(0, 0.0)
883- hbox.pack_start(label, True, True, 0)
884-
885- dialog.show_all()
886- result = dialog.run()
887-
888- if result == gtk.RESPONSE_OK:
889- crop = []
890- for spin in spin_list:
891- crop.append( spin.get_value()/100. )
892- for path in selection:
893- pos = model.get_iter(path)
894- for it in range(4):
895- model.set_value(pos, 8 + it, crop[it])
896- model.set_value(pos, 6, False) #rendering request
897- self.render()
898- elif result == gtk.RESPONSE_CANCEL:
899- print(_('Dialog closed'))
900- dialog.destroy()
901-
902- # =======================================================
903- def about_dialog(self, widget, data=None):
904- about_dialog = gtk.AboutDialog()
905- try:
906- about_dialog.set_transient_for(self.window)
907- about_dialog.set_modal(True)
908- except:
909- pass
910- # FIXME
911- about_dialog.set_name('PDF-Shuffler')
912- about_dialog.set_version('0.5.1')
913- about_dialog.set_comments(_(
914- 'PDF-Shuffler is a simple pyGTK utility which lets you merge, \
915-split and rearrange PDF documents. You can also rotate and crop individual \
916-pages of a pdf document.'))
917- about_dialog.set_authors(['Konstantinos Poulios',])
918- about_dialog.set_website_label('http://pdfshuffler.sourceforge.net/')
919- about_dialog.set_logo_icon_name('pdfshuffler')
920- about_dialog.connect('response', lambda w, a: about_dialog.destroy())
921- about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy())
922- about_dialog.show_all()
923-
924-# =======================================================
925-class PDF_Doc:
926- """Class handling pdf documents"""
927-
928- def __init__(self, filename, nfile, tmp_dir):
929-
930- self.filename = os.path.abspath(filename)
931- (self.path, self.shortname) = os.path.split(self.filename)
932- (self.shortname, self.ext) = os.path.splitext(self.shortname)
933- f = gio.File(filename)
934- mime_type = f.query_info('standard::content-type').get_content_type()
935- if mime_type == 'application/pdf':
936- self.nfile = nfile + 1
937- self.mtime = os.path.getmtime(filename)
938- self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile +
939- self.shortname + '.pdf')
940- shutil.copy(self.filename, self.copyname)
941- self.document = poppler.document_new_from_file("file://" + self.copyname, None)
942- self.npage = self.document.get_n_pages()
943- else:
944- self.nfile = 0
945-
946-
947-# =======================================================
948-class PDF_Renderer(threading.Thread,gobject.GObject):
949-
950- def __init__(self, model, pdfqueue, scale=1., width=100):
951- threading.Thread.__init__(self)
952- gobject.GObject.__init__(self)
953- self.model = model
954- self.scale = scale
955- self.default_width = width
956- self.pdfqueue = pdfqueue
957- self.quit = False
958- self.evnt = threading.Event()
959- self.paused = False
960-
961- def run(self):
962- while not self.quit:
963- rendered_all = True
964- for row in self.model:
965- if self.quit:
966- break
967- if not row[6]:
968- rendered_all = False
969- gtk.gdk.threads_enter()
970- try:
971- nfile = row[2]
972- npage = row[3]
973- angle = row[7]
974- crop = [row[8],row[9],row[10],row[11]]
975- pdfdoc = self.pdfqueue[nfile - 1]
976- thumbnail = self.load_pdf_thumbnail(pdfdoc, npage, angle, crop)
977- row[6] = True
978- row[4] = thumbnail.get_width()
979- row[1] = thumbnail
980- finally:
981- gtk.gdk.threads_leave()
982- if rendered_all:
983- if self.model.get_iter_first(): #just checking if model isn't empty
984- self.emit('reset_iv_width')
985- self.paused = True
986- self.evnt.wait()
987-
988- # =======================================================
989- def load_pdf_thumbnail(self, pdfdoc, npage, rotation=0, crop=[0.,0.,0.,0.]):
990- """Create pdf pixbuf"""
991-
992- page = pdfdoc.document.get_page(npage-1)
993- try:
994- pix_w, pix_h = page.get_size()
995- pix_w = int(pix_w * self.scale)
996- pix_h = int(pix_h * self.scale)
997- thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
998- 8, pix_w , pix_h)
999- page.render_to_pixbuf(0,0,pix_w,pix_h,self.scale,0,thumbnail)
1000- rotation = (-rotation) % 360
1001- rotation = ((rotation + 45) / 90) * 90
1002- thumbnail = thumbnail.rotate_simple(rotation)
1003- pix_w = thumbnail.get_width()
1004- pix_h = thumbnail.get_height()
1005- if crop != [0.,0.,0.,0.]:
1006- src_x = int( crop[0] * pix_w )
1007- src_y = int( crop[2] * pix_h )
1008- width = int( (1. - crop[0] - crop[1]) * pix_w )
1009- height = int( (1. - crop[2] - crop[3]) * pix_h )
1010- new_thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
1011- 8, width, height)
1012- thumbnail.copy_area(src_x, src_y, width, height,
1013- new_thumbnail, 0, 0)
1014- thumbnail = new_thumbnail
1015- pix_w = thumbnail.get_width()
1016- pix_h = thumbnail.get_height()
1017- except:
1018- pix_w = self.default_width
1019- pix_h = pix_w
1020- thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
1021- 8, pix_w, pix_h)
1022- pixbuf.fill(0xffffffff)
1023-
1024- #add border
1025- thickness = 3
1026- color = 0x000000FF
1027- canvas = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
1028- pix_w + thickness + 1,
1029- pix_h + thickness + 1)
1030- canvas.fill(color)
1031- thumbnail.copy_area(0, 0, pix_w, pix_h, canvas, 1, 1)
1032- thumbnail = canvas
1033-
1034- return thumbnail
1035-
1036-
1037-# =======================================================
1038-if __name__ == '__main__':
1039- PDFshuffler()
1040- gtk.gdk.threads_enter()
1041- gtk.main()
1042- gtk.gdk.threads_leave()
1043-
1044
1045=== added directory '.pc/install_and_look_for_UI_file_in_right_dirs.patch'
1046=== added directory '.pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler'
1047=== added file '.pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler/pdfshuffler.py'
1048--- .pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler/pdfshuffler.py 1970-01-01 00:00:00 +0000
1049+++ .pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler/pdfshuffler.py 2012-02-12 00:35:23 +0000
1050@@ -0,0 +1,1057 @@
1051+#!/usr/bin/python
1052+# -*- coding: utf-8 -*-
1053+
1054+"""
1055+
1056+ PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and
1057+ modification of PDF documents.
1058+ Copyright (C) 2008-2011 Konstantinos Poulios
1059+ <https://sourceforge.net/projects/pdfshuffler>
1060+
1061+ This file is part of PdfShuffler.
1062+
1063+ PdfShuffler is free software; you can redistribute it and/or modify
1064+ it under the terms of the GNU General Public License as published by
1065+ the Free Software Foundation; either version 3 of the License, or
1066+ (at your option) any later version.
1067+
1068+ This program is distributed in the hope that it will be useful,
1069+ but WITHOUT ANY WARRANTY; without even the implied warranty of
1070+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1071+ GNU General Public License for more details.
1072+
1073+ You should have received a copy of the GNU General Public License along
1074+ with this program; if not, write to the Free Software Foundation, Inc.,
1075+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1076+
1077+"""
1078+
1079+import os
1080+import shutil #needed for file operations like whole directory deletion
1081+import sys #needed for proccessing of command line args
1082+import urllib #needed to parse filename information passed by DnD
1083+import threading
1084+import tempfile
1085+from copy import copy
1086+
1087+import locale #for multilanguage support
1088+import gettext
1089+gettext.install('pdfshuffler', unicode=1)
1090+
1091+
1092+APPNAME = 'PdfShuffler' # PDF-Shuffler, PDFShuffler, pdfshuffler
1093+VERSION = '0.6.0'
1094+WEBSITE = 'http://pdfshuffler.sourceforge.net/'
1095+LICENSE = 'GNU General Public License (GPL) Version 3.'
1096+
1097+try:
1098+ import pygtk
1099+ pygtk.require('2.0')
1100+ import gtk
1101+ assert gtk.gtk_version >= (2, 10, 0)
1102+ assert gtk.pygtk_version >= (2, 10, 0)
1103+except AssertionError:
1104+ print('You do not have the required versions of GTK+ and PyGTK ' +
1105+ 'installed.\n\n' +
1106+ 'Installed GTK+ version is ' +
1107+ '.'.join([str(n) for n in gtk.gtk_version]) + '\n' +
1108+ 'Required GTK+ version is 2.10.0 or higher\n\n'
1109+ 'Installed PyGTK version is ' +
1110+ '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' +
1111+ 'Required PyGTK version is 2.10.0 or higher')
1112+ sys.exit(1)
1113+except:
1114+ print('PyGTK version 2.10.0 or higher is required to run this program.')
1115+ print('No version of PyGTK was found on your system.')
1116+ sys.exit(1)
1117+
1118+import gobject #to use custom signals
1119+import pango #to adjust the text alignment in CellRendererText
1120+import gio #to inquire mime types information
1121+import cairo
1122+
1123+import poppler #for the rendering of pdf pages
1124+from pyPdf import PdfFileWriter, PdfFileReader
1125+
1126+from cairorendering import CellRendererImage, CairoImage
1127+gobject.type_register(CellRendererImage)
1128+
1129+import time
1130+
1131+class PdfShuffler:
1132+ prefs = {
1133+ 'window width': min(700, gtk.gdk.screen_get_default().get_width() / 2),
1134+ 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50),
1135+ 'window x': 0,
1136+ 'window y': 0,
1137+ 'initial thumbnail size': 300,
1138+ 'initial zoom level': -14,
1139+ }
1140+
1141+ MODEL_ROW_INTERN = 1001
1142+ MODEL_ROW_EXTERN = 1002
1143+ TEXT_URI_LIST = 1003
1144+ MODEL_ROW_MOTION = 1004
1145+ TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN),
1146+ ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN),
1147+ ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)]
1148+ TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST),
1149+ ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)]
1150+
1151+ def __init__(self):
1152+ # Create the temporary directory
1153+ self.tmp_dir = tempfile.mkdtemp("pdfshuffler")
1154+ os.chmod(self.tmp_dir, 0700)
1155+
1156+ icon_theme = gtk.icon_theme_get_default()
1157+ try:
1158+ gtk.window_set_default_icon(icon_theme.load_icon("pdfshuffler", 64, 0))
1159+ except:
1160+ print(_("Can't load icon. Application is not installed correctly."))
1161+
1162+ # Import the user interface file, trying different possible locations
1163+ ui_path = '/usr/share/pdfshuffler/pdfshuffler.ui'
1164+ if not os.path.exists(ui_path):
1165+ ui_path = '/usr/local/share/applications/pdfshuffler/pdfshuffler.ui'
1166+ if not os.path.exists(ui_path):
1167+ parent_dir = os.path.dirname( \
1168+ os.path.dirname(os.path.realpath(__file__)))
1169+ ui_path = os.path.join(parent_dir, 'data', 'pdfshuffler.ui')
1170+ self.uiXML = gtk.Builder()
1171+ self.uiXML.add_from_file(ui_path)
1172+ self.uiXML.connect_signals(self)
1173+
1174+ # Create the main window, and attach delete_event signal to terminating
1175+ # the application
1176+ self.window = self.uiXML.get_object('main_window')
1177+ self.window.set_title(APPNAME)
1178+ self.window.set_border_width(0)
1179+ self.window.move(self.prefs['window x'], self.prefs['window y'])
1180+ self.window.set_default_size(self.prefs['window width'],
1181+ self.prefs['window height'])
1182+ self.window.connect('delete_event', self.close_application)
1183+ self.window.show_all()
1184+
1185+ # Create a scrolled window to hold the thumbnails-container
1186+ self.sw = self.uiXML.get_object('scrolledwindow')
1187+ self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
1188+ gtk.DEST_DEFAULT_HIGHLIGHT |
1189+ gtk.DEST_DEFAULT_DROP |
1190+ gtk.DEST_DEFAULT_MOTION,
1191+ self.TARGETS_SW,
1192+ gtk.gdk.ACTION_COPY |
1193+ gtk.gdk.ACTION_MOVE)
1194+ self.sw.connect('drag_data_received', self.sw_dnd_received_data)
1195+ self.sw.connect('button_press_event', self.sw_button_press_event)
1196+ self.sw.connect('scroll_event', self.sw_scroll_event)
1197+
1198+ # Create an alignment to keep the thumbnails center-aligned
1199+ align = gtk.Alignment(0.5, 0.5, 0, 0)
1200+ self.sw.add_with_viewport(align)
1201+
1202+ # Create ListStore model and IconView
1203+ self.model = gtk.ListStore(str, # 0.Text descriptor
1204+ CairoImage, # 1.Cached page image
1205+ int, # 2.Document number
1206+ int, # 3.Page number
1207+ float, # 4.Scale
1208+ str, # 5.Document filename
1209+ int, # 6.Rotation angle
1210+ float, # 7.Crop left
1211+ float, # 8.Crop right
1212+ float, # 9.Crop top
1213+ float, # 10.Crop bottom
1214+ int, # 11.Page width
1215+ int) # 12.Page height
1216+
1217+ self.zoom_set(self.prefs['initial zoom level'])
1218+ self.iv_col_width = self.prefs['initial thumbnail size']
1219+
1220+ self.iconview = gtk.IconView(self.model)
1221+ self.iconview.set_item_width(self.iv_col_width + 12)
1222+
1223+ self.cellthmb = CellRendererImage()
1224+ self.iconview.pack_start(self.cellthmb, False)
1225+ self.iconview.set_attributes(self.cellthmb, image=1,
1226+ scale=4, rotation=6, cropL=7, cropR=8, cropT=9, cropB=10,
1227+ width=11, height=12)
1228+
1229+# self.iconview.set_text_column(0)
1230+ self.celltxt = gtk.CellRendererText()
1231+ self.celltxt.set_property('width', self.iv_col_width)
1232+ self.celltxt.set_property('wrap-width', self.iv_col_width)
1233+ self.celltxt.set_property('alignment', pango.ALIGN_CENTER)
1234+ self.iconview.pack_start(self.celltxt, False)
1235+ self.iconview.set_attributes(self.celltxt, text=0)
1236+
1237+ self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE)
1238+ self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
1239+ self.TARGETS_IV,
1240+ gtk.gdk.ACTION_COPY |
1241+ gtk.gdk.ACTION_MOVE)
1242+ self.iconview.enable_model_drag_dest(self.TARGETS_IV,
1243+ gtk.gdk.ACTION_DEFAULT)
1244+ self.iconview.connect('drag_begin', self.iv_drag_begin)
1245+ self.iconview.connect('drag_data_get', self.iv_dnd_get_data)
1246+ self.iconview.connect('drag_data_received', self.iv_dnd_received_data)
1247+ self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete)
1248+ self.iconview.connect('drag_motion', self.iv_dnd_motion)
1249+ self.iconview.connect('drag_leave', self.iv_dnd_leave_end)
1250+ self.iconview.connect('drag_end', self.iv_dnd_leave_end)
1251+ self.iconview.connect('button_press_event', self.iv_button_press_event)
1252+ self.iv_auto_scroll_direction = 0
1253+
1254+ style = self.iconview.get_style().copy()
1255+ style_sw = self.sw.get_style()
1256+ for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE):
1257+ style.base[state] = style_sw.bg[gtk.STATE_NORMAL]
1258+ self.iconview.set_style(style)
1259+
1260+ align.add(self.iconview)
1261+
1262+ # Progress bar
1263+ self.progress_bar = self.uiXML.get_object('progressbar')
1264+ self.progress_bar_timeout_id = 0
1265+
1266+ # Define window callback function and show window
1267+ self.window.connect('size_allocate', self.on_window_size_request) # resize
1268+ self.window.connect('key_press_event', self.on_keypress_event ) # keypress
1269+ self.window.show_all()
1270+ self.progress_bar.hide_all()
1271+
1272+ # Creating the popup menu
1273+ self.popup = gtk.Menu()
1274+ popup_rotate_right = gtk.ImageMenuItem(_('_Rotate Right'))
1275+ popup_rotate_left = gtk.ImageMenuItem(_('Rotate _Left'))
1276+ popup_crop = gtk.MenuItem(_('C_rop...'))
1277+ popup_delete = gtk.ImageMenuItem(gtk.STOCK_DELETE)
1278+ popup_rotate_right.connect('activate', self.rotate_page_right)
1279+ popup_rotate_left.connect('activate', self.rotate_page_left)
1280+ popup_crop.connect('activate', self.crop_page_dialog)
1281+ popup_delete.connect('activate', self.clear_selected)
1282+ popup_rotate_right.show()
1283+ popup_rotate_left.show()
1284+ popup_crop.show()
1285+ popup_delete.show()
1286+ self.popup.append(popup_rotate_right)
1287+ self.popup.append(popup_rotate_left)
1288+ self.popup.append(popup_crop)
1289+ self.popup.append(popup_delete)
1290+
1291+ # Initializing variables
1292+ self.export_directory = os.getenv('HOME')
1293+ self.import_directory = self.export_directory
1294+ self.nfile = 0
1295+ self.iv_auto_scroll_timer = None
1296+ self.pdfqueue = []
1297+
1298+ gobject.type_register(PDF_Renderer)
1299+ gobject.signal_new('update_thumbnail', PDF_Renderer,
1300+ gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
1301+ [gobject.TYPE_INT, gobject.TYPE_PYOBJECT])
1302+ self.rendering_thread = 0
1303+
1304+ self.set_unsaved(False)
1305+
1306+ # Importing documents passed as command line arguments
1307+ for filename in sys.argv[1:]:
1308+ self.add_pdf_pages(filename)
1309+
1310+ def render(self):
1311+ if self.rendering_thread:
1312+ self.rendering_thread.quit = True
1313+ self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue)
1314+ self.rendering_thread.connect('update_thumbnail', self.update_thumbnail)
1315+ self.rendering_thread.start()
1316+
1317+ if self.progress_bar_timeout_id:
1318+ gobject.source_remove(self.progress_bar_timeout_id)
1319+ self.progress_bar_timout_id = \
1320+ gobject.timeout_add(50, self.progress_bar_timeout)
1321+
1322+ def set_unsaved(self, flag):
1323+ self.is_unsaved = flag
1324+ gobject.idle_add(self.retitle)
1325+
1326+ def retitle(self):
1327+ title = ''
1328+ if len(self.pdfqueue) == 1:
1329+ title += self.pdfqueue[0].filename
1330+ elif len(self.pdfqueue) == 0:
1331+ title += _("No document")
1332+ else:
1333+ title += _("Several documents")
1334+ if self.is_unsaved:
1335+ title += '*'
1336+ title += ' - ' + APPNAME
1337+ self.window.set_title(title)
1338+
1339+ def progress_bar_timeout(self):
1340+ cnt_finished = 0
1341+ cnt_all = 0
1342+ for row in self.model:
1343+ cnt_all += 1
1344+ if row[1].surface:
1345+ cnt_finished += 1
1346+ fraction = float(cnt_finished)/float(cnt_all)
1347+
1348+ self.progress_bar.set_fraction(fraction)
1349+ self.progress_bar.set_text(_('Rendering thumbnails... [%(i1)s/%(i2)s]')
1350+ % {'i1' : cnt_finished, 'i2' : cnt_all})
1351+ if fraction >= 0.999:
1352+ self.progress_bar.hide_all()
1353+ return False
1354+ elif not self.progress_bar.flags() & gtk.VISIBLE:
1355+ self.progress_bar.show_all()
1356+
1357+ return True
1358+
1359+ def update_thumbnail(self, object, num, thumbnail):
1360+ row = self.model[num]
1361+ row[4] = self.zoom_scale
1362+ row[1] = thumbnail
1363+
1364+ def on_window_size_request(self, window, event):
1365+ """Main Window resize - workaround for autosetting of
1366+ iconview cols no."""
1367+
1368+ #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152
1369+ col_num = 9 * window.get_size()[0] \
1370+ / (10 * (self.iv_col_width + self.iconview.get_column_spacing() * 2))
1371+ self.iconview.set_columns(col_num)
1372+
1373+ def update_geometry(self, iter):
1374+ """Recomputes the width and height of the rotated page and saves
1375+ the result in the ListStore"""
1376+
1377+ if not self.model.iter_is_valid(iter):
1378+ return
1379+
1380+ nfile, npage, rotation = self.model.get(iter, 2, 3, 6)
1381+ crop = self.model.get(iter, 7, 8, 9, 10)
1382+ page = self.pdfqueue[nfile-1].document.get_page(npage-1)
1383+ w0, h0 = page.get_size()
1384+
1385+ rotation = int(rotation) % 360
1386+ rotation = ((rotation + 45) / 90) * 90
1387+ if rotation == 90 or rotation == 270:
1388+ w1, h1 = h0, w0
1389+ else:
1390+ w1, h1 = w0, h0
1391+
1392+ self.model.set(iter, 11, w1, 12, h1)
1393+
1394+ def reset_iv_width(self, renderer=None):
1395+ """Reconfigures the width of the iconview columns"""
1396+
1397+ if not self.model.get_iter_first(): #just checking if model is empty
1398+ return
1399+
1400+ max_w = 10 + int(max(row[4]*row[11]*(1.-row[7]-row[8]) for row in self.model))
1401+ if max_w != self.iv_col_width:
1402+ self.iv_col_width = max_w
1403+ self.celltxt.set_property('width', self.iv_col_width)
1404+ self.celltxt.set_property('wrap-width', self.iv_col_width)
1405+ self.iconview.set_item_width(self.iv_col_width + 12) #-1)
1406+ self.on_window_size_request(self.window, None)
1407+
1408+ def on_keypress_event(self, widget, event):
1409+ """Keypress events in Main Window"""
1410+
1411+ #keyname = gtk.gdk.keyval_name(event.keyval)
1412+ if event.keyval == 65535: # Delete keystroke
1413+ self.clear_selected()
1414+
1415+ def close_application(self, widget, event=None, data=None):
1416+ """Termination"""
1417+
1418+ if self.rendering_thread:
1419+ self.rendering_thread.quit = True
1420+ while self.rendering_thread.isAlive():
1421+ time.sleep(0.5)
1422+
1423+ if os.path.isdir(self.tmp_dir):
1424+ shutil.rmtree(self.tmp_dir)
1425+ if gtk.main_level():
1426+ gtk.main_quit()
1427+ else:
1428+ sys.exit(0)
1429+ return False
1430+
1431+ def add_pdf_pages(self, filename,
1432+ firstpage=None, lastpage=None,
1433+ angle=0, crop=[0.,0.,0.,0.]):
1434+ """Add pages of a pdf document to the model"""
1435+
1436+ res = False
1437+ # Check if the document has already been loaded
1438+ pdfdoc = None
1439+ for it_pdfdoc in self.pdfqueue:
1440+ if os.path.isfile(it_pdfdoc.filename) and \
1441+ os.path.samefile(filename, it_pdfdoc.filename) and \
1442+ os.path.getmtime(filename) is it_pdfdoc.mtime:
1443+ pdfdoc = it_pdfdoc
1444+ break
1445+
1446+ if not pdfdoc:
1447+ pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir)
1448+ self.import_directory = os.path.split(filename)[0]
1449+ self.export_directory = self.import_directory
1450+ if pdfdoc.nfile != 0 and pdfdoc != []:
1451+ self.nfile = pdfdoc.nfile
1452+ self.pdfqueue.append(pdfdoc)
1453+ else:
1454+ return res
1455+
1456+ n_start = 1
1457+ n_end = pdfdoc.npage
1458+ if firstpage:
1459+ n_start = min(n_end, max(1, firstpage))
1460+ if lastpage:
1461+ n_end = max(n_start, min(n_end, lastpage))
1462+
1463+ for npage in range(n_start, n_end + 1):
1464+ descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)])
1465+ page = pdfdoc.document.get_page(npage-1)
1466+ w, h = page.get_size()
1467+ iter = self.model.append((descriptor, # 0
1468+ CairoImage(), # 1
1469+ pdfdoc.nfile, # 2
1470+ npage, # 3
1471+ self.zoom_scale, # 4
1472+ pdfdoc.filename, # 5
1473+ angle, # 6
1474+ crop[0],crop[1], # 7-8
1475+ crop[2],crop[3], # 9-10
1476+ w,h )) # 11-12
1477+ self.update_geometry(iter)
1478+ res = True
1479+
1480+ self.reset_iv_width()
1481+ gobject.idle_add(self.retitle)
1482+ if res:
1483+ gobject.idle_add(self.render)
1484+ return res
1485+
1486+ def choose_export_pdf_name(self, widget=None):
1487+ """Handles choosing a name for exporting """
1488+
1489+ chooser = gtk.FileChooserDialog(title=_('Export ...'),
1490+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
1491+ buttons=(gtk.STOCK_CANCEL,
1492+ gtk.RESPONSE_CANCEL,
1493+ gtk.STOCK_SAVE,
1494+ gtk.RESPONSE_OK))
1495+ chooser.set_do_overwrite_confirmation(True)
1496+ chooser.set_current_folder(self.export_directory)
1497+ filter_pdf = gtk.FileFilter()
1498+ filter_pdf.set_name(_('PDF files'))
1499+ filter_pdf.add_mime_type('application/pdf')
1500+ chooser.add_filter(filter_pdf)
1501+
1502+ filter_all = gtk.FileFilter()
1503+ filter_all.set_name(_('All files'))
1504+ filter_all.add_pattern('*')
1505+ chooser.add_filter(filter_all)
1506+
1507+ while True:
1508+ response = chooser.run()
1509+ if response == gtk.RESPONSE_OK:
1510+ file_out = chooser.get_filename()
1511+ (path, shortname) = os.path.split(file_out)
1512+ (shortname, ext) = os.path.splitext(shortname)
1513+ if ext.lower() != '.pdf':
1514+ file_out = file_out + '.pdf'
1515+ try:
1516+ self.export_to_file(file_out)
1517+ self.export_directory = path
1518+ self.set_unsaved(False)
1519+ except Exception, e:
1520+ chooser.destroy()
1521+ error_msg_dlg = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
1522+ type=gtk.MESSAGE_ERROR,
1523+ message_format=str(e),
1524+ buttons=gtk.BUTTONS_OK)
1525+ response = error_msg_dlg.run()
1526+ if response == gtk.RESPONSE_OK:
1527+ error_msg_dlg.destroy()
1528+ return
1529+ break
1530+ chooser.destroy()
1531+
1532+ def export_to_file(self, file_out):
1533+ """Export to file"""
1534+
1535+ pdf_output = PdfFileWriter()
1536+ pdf_input = []
1537+ for pdfdoc in self.pdfqueue:
1538+ pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb'))
1539+ if pdfdoc_inp.getIsEncrypted():
1540+ try: # Workaround for lp:#355479
1541+ stat = pdfdoc_inp.decrypt('')
1542+ except:
1543+ stat = 0
1544+ if (stat!=1):
1545+ errmsg = _('File %s is encrypted.\n'
1546+ 'Support for encrypted files has not been implemented yet.\n'
1547+ 'File export failed.') % pdfdoc.filename
1548+ raise Exception, errmsg
1549+ #FIXME
1550+ #else
1551+ # ask for password and decrypt file
1552+ pdf_input.append(pdfdoc_inp)
1553+
1554+ for row in self.model:
1555+ # add pages from input to output document
1556+ nfile = row[2]
1557+ npage = row[3]
1558+ current_page = copy(pdf_input[nfile-1].getPage(npage-1))
1559+ angle = row[6]
1560+ angle0 = current_page.get("/Rotate",0)
1561+ crop = [row[7],row[8],row[9],row[10]]
1562+ if angle != 0:
1563+ current_page.rotateClockwise(angle)
1564+ if crop != [0.,0.,0.,0.]:
1565+ rotate_times = (((angle + angle0) % 360 + 45) / 90) % 4
1566+ crop_init = crop
1567+ if rotate_times != 0:
1568+ perm = [0,2,1,3]
1569+ for it in range(rotate_times):
1570+ perm.append(perm.pop(0))
1571+ perm.insert(1,perm.pop(2))
1572+ crop = [crop_init[perm[side]] for side in range(4)]
1573+ #(x1, y1) = current_page.cropBox.lowerLeft
1574+ #(x2, y2) = current_page.cropBox.upperRight
1575+ (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft]
1576+ (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight]
1577+ x1_new = int(x1 + (x2-x1) * crop[0])
1578+ x2_new = int(x2 - (x2-x1) * crop[1])
1579+ y1_new = int(y1 + (y2-y1) * crop[3])
1580+ y2_new = int(y2 - (y2-y1) * crop[2])
1581+ #current_page.cropBox.lowerLeft = (x1_new, y1_new)
1582+ #current_page.cropBox.upperRight = (x2_new, y2_new)
1583+ current_page.mediaBox.lowerLeft = (x1_new, y1_new)
1584+ current_page.mediaBox.upperRight = (x2_new, y2_new)
1585+
1586+ pdf_output.addPage(current_page)
1587+
1588+ # finally, write "output" to document-output.pdf
1589+ print(_('exporting to:'), file_out)
1590+ pdf_output.write(file(file_out, 'wb'))
1591+
1592+ def on_action_add_doc_activate(self, widget, data=None):
1593+ """Import doc"""
1594+
1595+ chooser = gtk.FileChooserDialog(title=_('_Import...'),
1596+ action=gtk.FILE_CHOOSER_ACTION_OPEN,
1597+ buttons=(gtk.STOCK_CANCEL,
1598+ gtk.RESPONSE_CANCEL,
1599+ gtk.STOCK_OPEN,
1600+ gtk.RESPONSE_OK))
1601+ chooser.set_current_folder(self.import_directory)
1602+ chooser.set_select_multiple(True)
1603+
1604+ filter_all = gtk.FileFilter()
1605+ filter_all.set_name(_('All files'))
1606+ filter_all.add_pattern('*')
1607+ chooser.add_filter(filter_all)
1608+
1609+ filter_pdf = gtk.FileFilter()
1610+ filter_pdf.set_name(_('PDF files'))
1611+ filter_pdf.add_mime_type('application/pdf')
1612+ chooser.add_filter(filter_pdf)
1613+ chooser.set_filter(filter_pdf)
1614+
1615+ response = chooser.run()
1616+ if response == gtk.RESPONSE_OK:
1617+ for filename in chooser.get_filenames():
1618+ if os.path.isfile(filename):
1619+ # FIXME
1620+ f = gio.File(filename)
1621+ f_info = f.query_info('standard::content-type')
1622+ mime_type = f_info.get_content_type()
1623+ expected_mime_type = 'application/pdf'
1624+
1625+ if mime_type == expected_mime_type:
1626+ self.add_pdf_pages(filename)
1627+ elif mime_type[:34] == 'application/vnd.oasis.opendocument':
1628+ print(_('OpenDocument not supported yet!'))
1629+ elif mime_type[:5] == 'image':
1630+ print(_('Image file not supported yet!'))
1631+ else:
1632+ print(_('File type not supported!'))
1633+ else:
1634+ print(_('File %s does not exist') % filename)
1635+ elif response == gtk.RESPONSE_CANCEL:
1636+ print(_('Closed, no files selected'))
1637+ chooser.destroy()
1638+ gobject.idle_add(self.retitle)
1639+
1640+ def clear_selected(self, button=None):
1641+ """Removes the selected elements in the IconView"""
1642+
1643+ model = self.iconview.get_model()
1644+ selection = self.iconview.get_selected_items()
1645+ if selection:
1646+ selection.sort(reverse=True)
1647+ self.set_unsaved(True)
1648+ for path in selection:
1649+ iter = model.get_iter(path)
1650+ model.remove(iter)
1651+ path = selection[-1]
1652+ self.iconview.select_path(path)
1653+ if not self.iconview.path_is_selected(path):
1654+ if len(model) > 0: # select the last row
1655+ row = model[-1]
1656+ path = row.path
1657+ self.iconview.select_path(path)
1658+ self.iconview.grab_focus()
1659+
1660+ def iv_drag_begin(self, iconview, context):
1661+ """Sets custom icon on drag begin for multiple items selected"""
1662+
1663+ if len(iconview.get_selected_items()) > 1:
1664+ iconview.stop_emission('drag_begin')
1665+ context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0)
1666+
1667+ def iv_dnd_get_data(self, iconview, context,
1668+ selection_data, target_id, etime):
1669+ """Handles requests for data by drag and drop in iconview"""
1670+
1671+ model = iconview.get_model()
1672+ selection = self.iconview.get_selected_items()
1673+ selection.sort(key=lambda x: x[0])
1674+ data = []
1675+ for path in selection:
1676+ if selection_data.target == 'MODEL_ROW_INTERN':
1677+ data.append(str(path[0]))
1678+ elif selection_data.target == 'MODEL_ROW_EXTERN':
1679+ iter = model.get_iter(path)
1680+ nfile, npage, angle = model.get(iter, 2, 3, 6)
1681+ crop = model.get(iter, 7, 8, 9, 10)
1682+ pdfdoc = self.pdfqueue[nfile - 1]
1683+ data.append('\n'.join([pdfdoc.filename,
1684+ str(npage),
1685+ str(angle)] +
1686+ [str(side) for side in crop]))
1687+ if data:
1688+ data = '\n;\n'.join(data)
1689+ selection_data.set(selection_data.target, 8, data)
1690+
1691+ def iv_dnd_received_data(self, iconview, context, x, y,
1692+ selection_data, target_id, etime):
1693+ """Handles received data by drag and drop in iconview"""
1694+
1695+ model = iconview.get_model()
1696+ data = selection_data.data
1697+ if data:
1698+ data = data.split('\n;\n')
1699+ drop_info = iconview.get_dest_item_at_pos(x, y)
1700+ iter_to = None
1701+ if drop_info:
1702+ path, position = drop_info
1703+ ref_to = gtk.TreeRowReference(model,path)
1704+ else:
1705+ position = gtk.ICON_VIEW_DROP_RIGHT
1706+ if len(model) > 0: #find the iterator of the last row
1707+ row = model[-1]
1708+ path = row.path
1709+ ref_to = gtk.TreeRowReference(model,path)
1710+ if ref_to:
1711+ before = (position == gtk.ICON_VIEW_DROP_LEFT
1712+ or position == gtk.ICON_VIEW_DROP_ABOVE)
1713+ #if target_id == self.MODEL_ROW_INTERN:
1714+ if selection_data.target == 'MODEL_ROW_INTERN':
1715+ if before:
1716+ data.sort(key=int)
1717+ else:
1718+ data.sort(key=int,reverse=True)
1719+ ref_from_list = [gtk.TreeRowReference(model,path)
1720+ for path in data]
1721+ for ref_from in ref_from_list:
1722+ path = ref_to.get_path()
1723+ iter_to = model.get_iter(path)
1724+ path = ref_from.get_path()
1725+ iter_from = model.get_iter(path)
1726+ row = model[iter_from]
1727+ if before:
1728+ model.insert_before(iter_to, row)
1729+ else:
1730+ model.insert_after(iter_to, row)
1731+ if context.action == gtk.gdk.ACTION_MOVE:
1732+ for ref_from in ref_from_list:
1733+ path = ref_from.get_path()
1734+ iter_from = model.get_iter(path)
1735+ model.remove(iter_from)
1736+
1737+ #elif target_id == self.MODEL_ROW_EXTERN:
1738+ elif selection_data.target == 'MODEL_ROW_EXTERN':
1739+ if not before:
1740+ data.reverse()
1741+ while data:
1742+ tmp = data.pop(0).split('\n')
1743+ filename = tmp[0]
1744+ npage, angle = [int(k) for k in tmp[1:3]]
1745+ crop = [float(side) for side in tmp[3:7]]
1746+ if self.add_pdf_pages(filename, npage, npage,
1747+ angle, crop):
1748+ if len(model) > 0:
1749+ path = ref_to.get_path()
1750+ iter_to = model.get_iter(path)
1751+ row = model[-1] #the last row
1752+ path = row.path
1753+ iter_from = model.get_iter(path)
1754+ if before:
1755+ model.move_before(iter_from, iter_to)
1756+ else:
1757+ model.move_after(iter_from, iter_to)
1758+ if context.action == gtk.gdk.ACTION_MOVE:
1759+ context.finish(True, True, etime)
1760+
1761+ def iv_dnd_data_delete(self, widget, context):
1762+ """Deletes dnd items after a successful move operation"""
1763+
1764+ model = self.iconview.get_model()
1765+ selection = self.iconview.get_selected_items()
1766+ ref_del_list = [gtk.TreeRowReference(model,path) for path in selection]
1767+ for ref_del in ref_del_list:
1768+ path = ref_del.get_path()
1769+ iter = model.get_iter(path)
1770+ model.remove(iter)
1771+
1772+ def iv_dnd_motion(self, iconview, context, x, y, etime):
1773+ """Handles the drag-motion signal in order to auto-scroll the view"""
1774+
1775+ autoscroll_area = 40
1776+ sw_vadj = self.sw.get_vadjustment()
1777+ sw_height = self.sw.get_allocation().height
1778+ if y -sw_vadj.get_value() < autoscroll_area:
1779+ if not self.iv_auto_scroll_timer:
1780+ self.iv_auto_scroll_direction = gtk.DIR_UP
1781+ self.iv_auto_scroll_timer = gobject.timeout_add(150,
1782+ self.iv_auto_scroll)
1783+ elif y -sw_vadj.get_value() > sw_height - autoscroll_area:
1784+ if not self.iv_auto_scroll_timer:
1785+ self.iv_auto_scroll_direction = gtk.DIR_DOWN
1786+ self.iv_auto_scroll_timer = gobject.timeout_add(150,
1787+ self.iv_auto_scroll)
1788+ elif self.iv_auto_scroll_timer:
1789+ gobject.source_remove(self.iv_auto_scroll_timer)
1790+ self.iv_auto_scroll_timer = None
1791+
1792+ def iv_dnd_leave_end(self, widget, context, ignored=None):
1793+ """Ends the auto-scroll during DND"""
1794+
1795+ if self.iv_auto_scroll_timer:
1796+ gobject.source_remove(self.iv_auto_scroll_timer)
1797+ self.iv_auto_scroll_timer = None
1798+
1799+ def iv_auto_scroll(self):
1800+ """Timeout routine for auto-scroll"""
1801+
1802+ sw_vadj = self.sw.get_vadjustment()
1803+ sw_vpos = sw_vadj.get_value()
1804+ if self.iv_auto_scroll_direction == gtk.DIR_UP:
1805+ sw_vpos -= sw_vadj.step_increment
1806+ sw_vadj.set_value(max(sw_vpos, sw_vadj.lower))
1807+ elif self.iv_auto_scroll_direction == gtk.DIR_DOWN:
1808+ sw_vpos += sw_vadj.step_increment
1809+ sw_vadj.set_value(min(sw_vpos, sw_vadj.upper - sw_vadj.page_size))
1810+ return True #call me again
1811+
1812+ def iv_button_press_event(self, iconview, event):
1813+ """Manages mouse clicks on the iconview"""
1814+
1815+ if event.button == 3:
1816+ x = int(event.x)
1817+ y = int(event.y)
1818+ time = event.time
1819+ path = iconview.get_path_at_pos(x, y)
1820+ selection = iconview.get_selected_items()
1821+ if path:
1822+ if path not in selection:
1823+ iconview.unselect_all()
1824+ iconview.select_path(path)
1825+ iconview.grab_focus()
1826+ self.popup.popup(None, None, None, event.button, time)
1827+ return 1
1828+
1829+ def sw_dnd_received_data(self, scrolledwindow, context, x, y,
1830+ selection_data, target_id, etime):
1831+ """Handles received data by drag and drop in scrolledwindow"""
1832+
1833+ data = selection_data.data
1834+ if target_id == self.MODEL_ROW_EXTERN:
1835+ self.model
1836+ if data:
1837+ data = data.split('\n;\n')
1838+ while data:
1839+ tmp = data.pop(0).split('\n')
1840+ filename = tmp[0]
1841+ npage, angle = [int(k) for k in tmp[1:3]]
1842+ crop = [float(side) for side in tmp[3:7]]
1843+ if self.add_pdf_pages(filename, npage, npage, angle, crop):
1844+ if context.action == gtk.gdk.ACTION_MOVE:
1845+ context.finish(True, True, etime)
1846+ elif target_id == self.TEXT_URI_LIST:
1847+ uri = data.strip()
1848+ uri_splitted = uri.split() # we may have more than one file dropped
1849+ for uri in uri_splitted:
1850+ filename = self.get_file_path_from_dnd_dropped_uri(uri)
1851+ if os.path.isfile(filename): # is it file?
1852+ self.add_pdf_pages(filename)
1853+
1854+ def sw_button_press_event(self, scrolledwindow, event):
1855+ """Unselects all items in iconview on mouse click in scrolledwindow"""
1856+
1857+ if event.button == 1:
1858+ self.iconview.unselect_all()
1859+
1860+ def sw_scroll_event(self, scrolledwindow, event):
1861+ """Manages mouse scroll events in scrolledwindow"""
1862+
1863+ if event.state & gtk.gdk.CONTROL_MASK:
1864+ if event.direction == gtk.gdk.SCROLL_UP:
1865+ self.zoom_change(1)
1866+ return 1
1867+ elif event.direction == gtk.gdk.SCROLL_DOWN:
1868+ self.zoom_change(-1)
1869+ return 1
1870+
1871+ def zoom_set(self, level):
1872+ """Sets the zoom level"""
1873+ self.zoom_level = max(min(level, 5), -24)
1874+ self.zoom_scale = 1.1 ** self.zoom_level
1875+ for row in self.model:
1876+ row[4] = self.zoom_scale
1877+ self.reset_iv_width()
1878+
1879+ def zoom_change(self, step=5):
1880+ """Modifies the zoom level"""
1881+ self.zoom_set(self.zoom_level + step)
1882+
1883+ def zoom_in(self, widget=None):
1884+ """Increases the zoom level by 5 steps"""
1885+ self.zoom_change(5)
1886+
1887+ def zoom_out(self, widget=None, step=5):
1888+ """Reduces the zoom level by 5 steps"""
1889+ self.zoom_change(-5)
1890+
1891+ def get_file_path_from_dnd_dropped_uri(self, uri):
1892+ """Extracts the path from an uri"""
1893+
1894+ path = urllib.url2pathname(uri) # escape special chars
1895+ path = path.strip('\r\n\x00') # remove \r\n and NULL
1896+
1897+ # get the path to file
1898+ if path.startswith('file:\\\\\\'): # windows
1899+ path = path[8:] # 8 is len('file:///')
1900+ elif path.startswith('file://'): # nautilus, rox
1901+ path = path[7:] # 7 is len('file://')
1902+ elif path.startswith('file:'): # xffm
1903+ path = path[5:] # 5 is len('file:')
1904+ return path
1905+
1906+ def rotate_page_right(self, widget, data=None):
1907+ self.rotate_page(90)
1908+
1909+ def rotate_page_left(self, widget, data=None):
1910+ self.rotate_page(-90)
1911+
1912+ def rotate_page(self, angle):
1913+ """Rotates the selected page in the IconView"""
1914+
1915+ model = self.iconview.get_model()
1916+ selection = self.iconview.get_selected_items()
1917+ if len(selection) > 0:
1918+ self.set_unsaved(True)
1919+ rotate_times = (((-angle) % 360 + 45) / 90) % 4
1920+ if rotate_times is not 0:
1921+ for path in selection:
1922+ iter = model.get_iter(path)
1923+ nfile = model.get_value(iter, 2)
1924+ npage = model.get_value(iter, 3)
1925+
1926+ crop = [0.,0.,0.,0.]
1927+ perm = [0,2,1,3]
1928+ for it in range(rotate_times):
1929+ perm.append(perm.pop(0))
1930+ perm.insert(1,perm.pop(2))
1931+ crop = [model.get_value(iter, 7 + perm[side]) for side in range(4)]
1932+ for side in range(4):
1933+ model.set_value(iter, 7 + side, crop[side])
1934+
1935+ new_angle = model.get_value(iter, 6) + int(angle)
1936+ new_angle = new_angle % 360
1937+ model.set_value(iter, 6, new_angle)
1938+ self.update_geometry(iter)
1939+ self.reset_iv_width()
1940+
1941+ def crop_page_dialog(self, widget):
1942+ """Opens a dialog box to define margins for page cropping"""
1943+
1944+ sides = ('L', 'R', 'T', 'B')
1945+ side_names = {'L':_('Left'), 'R':_('Right'),
1946+ 'T':_('Top'), 'B':_('Bottom') }
1947+ opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' }
1948+
1949+ def set_crop_value(spinbutton, side):
1950+ opp_side = opposite_sides[side]
1951+ pos = sides.index(opp_side)
1952+ adj = spin_list[pos].get_adjustment()
1953+ adj.set_upper(99.0 - spinbutton.get_value())
1954+
1955+ model = self.iconview.get_model()
1956+ selection = self.iconview.get_selected_items()
1957+
1958+ crop = [0.,0.,0.,0.]
1959+ if selection:
1960+ path = selection[0]
1961+ pos = model.get_iter(path)
1962+ crop = [model.get_value(pos, 7 + side) for side in range(4)]
1963+
1964+ dialog = gtk.Dialog(title=(_('Crop Selected Pages')),
1965+ parent=self.window,
1966+ flags=gtk.DIALOG_MODAL,
1967+ buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
1968+ gtk.STOCK_OK, gtk.RESPONSE_OK))
1969+ dialog.set_size_request(340, 250)
1970+ dialog.set_default_response(gtk.RESPONSE_OK)
1971+
1972+ frame = gtk.Frame(_('Crop Margins'))
1973+ dialog.vbox.pack_start(frame, False, False, 20)
1974+
1975+ vbox = gtk.VBox(False, 0)
1976+ frame.add(vbox)
1977+
1978+ spin_list = []
1979+ units = 2 * [_('% of width')] + 2 * [_('% of height')]
1980+ for side in sides:
1981+ hbox = gtk.HBox(True, 0)
1982+ vbox.pack_start(hbox, False, False, 5)
1983+
1984+ label = gtk.Label(side_names[side])
1985+ label.set_alignment(0, 0.0)
1986+ hbox.pack_start(label, True, True, 20)
1987+
1988+ adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0)
1989+ spin = gtk.SpinButton(adj, 0, 1)
1990+ spin.set_activates_default(True)
1991+ spin.connect('value-changed', set_crop_value, side)
1992+ spin_list.append(spin)
1993+ hbox.pack_start(spin, False, False, 30)
1994+
1995+ label = gtk.Label(units.pop(0))
1996+ label.set_alignment(0, 0.0)
1997+ hbox.pack_start(label, True, True, 0)
1998+
1999+ dialog.show_all()
2000+ result = dialog.run()
2001+
2002+ if result == gtk.RESPONSE_OK:
2003+ modified = False
2004+ crop = [spin.get_value()/100. for spin in spin_list]
2005+ for path in selection:
2006+ pos = model.get_iter(path)
2007+ for it in range(4):
2008+ old_val = model.get_value(pos, 7 + it)
2009+ model.set_value(pos, 7 + it, crop[it])
2010+ if crop[it] != old_val:
2011+ modified = True
2012+ self.update_geometry(pos)
2013+ if modified:
2014+ self.set_unsaved(True)
2015+ self.reset_iv_width()
2016+ elif result == gtk.RESPONSE_CANCEL:
2017+ print(_('Dialog closed'))
2018+ dialog.destroy()
2019+
2020+ def about_dialog(self, widget, data=None):
2021+ about_dialog = gtk.AboutDialog()
2022+ try:
2023+ about_dialog.set_transient_for(self.window)
2024+ about_dialog.set_modal(True)
2025+ except:
2026+ pass
2027+ # FIXME
2028+ about_dialog.set_name(APPNAME)
2029+ about_dialog.set_version(VERSION)
2030+ about_dialog.set_comments(_(
2031+ '%s is a tool for rearranging and modifying PDF files.' \
2032+ 'Developed using GTK+ and Python') % APPNAME)
2033+ about_dialog.set_authors(['Konstantinos Poulios',])
2034+ about_dialog.set_website_label(WEBSITE)
2035+ about_dialog.set_logo_icon_name('pdfshuffler')
2036+ about_dialog.set_license(LICENSE)
2037+ about_dialog.connect('response', lambda w, a: about_dialog.destroy())
2038+ about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy())
2039+ about_dialog.show_all()
2040+
2041+
2042+class PDF_Doc:
2043+ """Class handling PDF documents"""
2044+
2045+ def __init__(self, filename, nfile, tmp_dir):
2046+
2047+ self.filename = os.path.abspath(filename)
2048+ (self.path, self.shortname) = os.path.split(self.filename)
2049+ (self.shortname, self.ext) = os.path.splitext(self.shortname)
2050+ f = gio.File(filename)
2051+ mime_type = f.query_info('standard::content-type').get_content_type()
2052+ expected_mime_type = 'application/pdf'
2053+ file_prefix = 'file://'
2054+
2055+ if mime_type == expected_mime_type:
2056+ self.nfile = nfile + 1
2057+ self.mtime = os.path.getmtime(filename)
2058+ self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile +
2059+ self.shortname + '.pdf')
2060+ shutil.copy(self.filename, self.copyname)
2061+ self.document = poppler.document_new_from_file (file_prefix + self.copyname, None)
2062+ self.npage = self.document.get_n_pages()
2063+ else:
2064+ self.nfile = 0
2065+ self.npage = 0
2066+
2067+
2068+class PDF_Renderer(threading.Thread,gobject.GObject):
2069+
2070+ def __init__(self, model, pdfqueue):
2071+ threading.Thread.__init__(self)
2072+ gobject.GObject.__init__(self)
2073+ self.model = model
2074+ self.pdfqueue = pdfqueue
2075+ self.quit = False
2076+
2077+ def run(self):
2078+ for idx, row in enumerate(self.model):
2079+ if self.quit:
2080+ return
2081+ if not row[1].surface:
2082+ try:
2083+ nfile = row[2]
2084+ npage = row[3]
2085+ pdfdoc = self.pdfqueue[nfile - 1]
2086+ page = pdfdoc.document.get_page(npage-1)
2087+ w, h = page.get_size()
2088+ thumbnail = CairoImage(int(w), int(h))
2089+ cr = cairo.Context(thumbnail.surface)
2090+ page.render(cr)
2091+ time.sleep(0.003)
2092+ gobject.idle_add(self.emit,'update_thumbnail', idx, thumbnail,
2093+ priority=gobject.PRIORITY_LOW)
2094+ except Exception,e:
2095+ print e
2096+
2097+
2098+def main():
2099+ """This function starts PdfShuffler"""
2100+ gtk.gdk.threads_init()
2101+ gobject.threads_init()
2102+ PdfShuffler()
2103+ gtk.main()
2104+
2105+if __name__ == '__main__':
2106+ main()
2107+
2108
2109=== added file '.pc/install_and_look_for_UI_file_in_right_dirs.patch/setup.py'
2110--- .pc/install_and_look_for_UI_file_in_right_dirs.patch/setup.py 1970-01-01 00:00:00 +0000
2111+++ .pc/install_and_look_for_UI_file_in_right_dirs.patch/setup.py 2012-02-12 00:35:23 +0000
2112@@ -0,0 +1,66 @@
2113+#!/usr/bin/python
2114+
2115+#
2116+# PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and
2117+# modification of PDF documents.
2118+# Copyright (C) 2008-2011 Konstantinos Poulios
2119+# <https://sourceforge.net/projects/pdfshuffler>
2120+#
2121+# This program is free software; you can redistribute it and/or modify
2122+# it under the terms of the GNU General Public License as published by
2123+# the Free Software Foundation; either version 3 of the License, or
2124+# (at your option) any later version.
2125+#
2126+# This program is distributed in the hope that it will be useful,
2127+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2128+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2129+# GNU General Public License for more details.
2130+#
2131+# You should have received a copy of the GNU General Public License along
2132+# with this program; if not, write to the Free Software Foundation, Inc.,
2133+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2134+#
2135+
2136+import os
2137+import re
2138+from distutils.core import setup
2139+
2140+data_files=[('share/applications/pdfshuffler', ['data/pdfshuffler.ui']),
2141+ ('share/applications', ['data/pdfshuffler.desktop']),
2142+ ('share/man/man1', ['doc/pdfshuffler.1']),
2143+ ('share/pixmaps', ['data/pdfshuffler.svg']),
2144+ ('share/pixmaps', ['data/pdfshuffler.png']) ]
2145+
2146+
2147+# Freshly generate .mo from .po, add to data_files:
2148+if os.path.isdir('mo/'):
2149+ os.system ('rm -r mo/')
2150+for name in os.listdir('po'):
2151+ m = re.match(r'(.+)\.po$', name)
2152+ if m != None:
2153+ lang = m.group(1)
2154+ out_dir = 'mo/%s/LC_MESSAGES' % lang
2155+ out_name = os.path.join(out_dir, 'pdfshuffler.mo')
2156+ install_dir = 'share/locale/%s/LC_MESSAGES/' % lang
2157+ os.makedirs(out_dir)
2158+ os.system('msgfmt -o %s po/%s' % (out_name, name))
2159+ data_files.append((install_dir, [out_name]))
2160+
2161+setup(name='pdfshuffler',
2162+ version='0.6.0',
2163+ author='Konstantinos Poulios',
2164+ author_email='logari81 at gmail dot com',
2165+ description='A simple application for PDF Merging, Rearranging, and Splitting',
2166+ url = 'https://sourceforge.net/projects/pdfshuffler',
2167+ license='GNU GPL-3',
2168+ scripts=['bin/pdfshuffler'],
2169+ packages=['pdfshuffler'],
2170+ data_files=data_files
2171+ )
2172+
2173+# Clean up temporary files
2174+if os.path.isdir('mo/'):
2175+ os.system ('rm -r mo/')
2176+if os.path.isdir('build/'):
2177+ os.system ('rm -r build/')
2178+
2179
2180=== modified file 'TODO'
2181--- TODO 2011-02-15 00:43:28 +0000
2182+++ TODO 2012-02-12 00:35:23 +0000
2183@@ -11,5 +11,3 @@
2184
2185 * Add support for encrypted files.
2186
2187-* Show a progress bar while loading a document.
2188-
2189
2190=== added directory 'bin'
2191=== added file 'bin/pdfshuffler'
2192--- bin/pdfshuffler 1970-01-01 00:00:00 +0000
2193+++ bin/pdfshuffler 2012-02-12 00:35:23 +0000
2194@@ -0,0 +1,33 @@
2195+#!/usr/bin/python
2196+# -*- coding: utf-8 -*-
2197+
2198+"""
2199+
2200+ PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and
2201+ modification of PDF documents.
2202+ Copyright (C) 2008-2011 Konstantinos Poulios
2203+ <https://sourceforge.net/projects/pdfshuffler>
2204+
2205+ This file is part of PdfShuffler.
2206+
2207+ PdfShuffler is free software; you can redistribute it and/or modify
2208+ it under the terms of the GNU General Public License as published by
2209+ the Free Software Foundation; either version 3 of the License, or
2210+ (at your option) any later version.
2211+
2212+ This program is distributed in the hope that it will be useful,
2213+ but WITHOUT ANY WARRANTY; without even the implied warranty of
2214+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2215+ GNU General Public License for more details.
2216+
2217+ You should have received a copy of the GNU General Public License along
2218+ with this program; if not, write to the Free Software Foundation, Inc.,
2219+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2220+
2221+"""
2222+
2223+try:
2224+ from pdfshuffler.pdfshuffler import main
2225+ main()
2226+except ImportError:
2227+ print('Error: could not import pdfshuffler')
2228
2229=== added file 'data/pdfshuffler.ui'
2230--- data/pdfshuffler.ui 1970-01-01 00:00:00 +0000
2231+++ data/pdfshuffler.ui 2012-02-12 00:35:23 +0000
2232@@ -0,0 +1,359 @@
2233+<?xml version="1.0"?>
2234+<interface>
2235+ <requires lib="gtk+" version="2.16"/>
2236+ <!-- interface-naming-policy project-wide -->
2237+ <object class="GtkWindow" id="main_window">
2238+ <child>
2239+ <object class="GtkVBox" id="main_vbox">
2240+ <property name="visible">True</property>
2241+ <child>
2242+ <object class="GtkMenuBar" id="menubar">
2243+ <property name="visible">True</property>
2244+ <child>
2245+ <object class="GtkMenuItem" id="menuitem_file">
2246+ <property name="visible">True</property>
2247+ <property name="label" translatable="yes">_File</property>
2248+ <property name="use_underline">True</property>
2249+ <child type="submenu">
2250+ <object class="GtkMenu" id="menu_file">
2251+ <property name="visible">True</property>
2252+ <child>
2253+ <object class="GtkImageMenuItem" id="imagemenuitem_import">
2254+ <property name="label">gtk-open</property>
2255+ <property name="visible">True</property>
2256+ <property name="use_underline">True</property>
2257+ <property name="use_stock">True</property>
2258+ <signal name="activate" handler="on_action_add_doc_activate"/>
2259+ </object>
2260+ </child>
2261+ <child>
2262+ <object class="GtkImageMenuItem" id="imagemenuitem_save">
2263+ <property name="label">gtk-save</property>
2264+ <property name="visible">True</property>
2265+ <property name="sensitive">False</property>
2266+ <property name="use_underline">True</property>
2267+ <property name="use_stock">True</property>
2268+ </object>
2269+ </child>
2270+ <child>
2271+ <object class="GtkImageMenuItem" id="imagemenuitem_save_as">
2272+ <property name="label">gtk-save-as</property>
2273+ <property name="visible">True</property>
2274+ <property name="use_underline">True</property>
2275+ <property name="use_stock">True</property>
2276+ <signal name="activate" handler="choose_export_pdf_name"/>
2277+ </object>
2278+ </child>
2279+ <child>
2280+ <object class="GtkSeparatorMenuItem" id="separatormenuitem_file">
2281+ <property name="visible">True</property>
2282+ </object>
2283+ </child>
2284+ <child>
2285+ <object class="GtkImageMenuItem" id="imagemenuitem_quit">
2286+ <property name="label">gtk-quit</property>
2287+ <property name="visible">True</property>
2288+ <property name="use_underline">True</property>
2289+ <property name="use_stock">True</property>
2290+ <signal name="activate" handler="close_application"/>
2291+ </object>
2292+ </child>
2293+ </object>
2294+ </child>
2295+ </object>
2296+ </child>
2297+ <child>
2298+ <object class="GtkMenuItem" id="menuitem_edit">
2299+ <property name="visible">True</property>
2300+ <property name="label" translatable="yes">_Edit</property>
2301+ <property name="use_underline">True</property>
2302+ <child type="submenu">
2303+ <object class="GtkMenu" id="menu_edit">
2304+ <property name="visible">True</property>
2305+ <child>
2306+ <object class="GtkImageMenuItem" id="imagemenuitem_undo">
2307+ <property name="label">gtk-undo</property>
2308+ <property name="sensitive">False</property>
2309+ <property name="use_underline">True</property>
2310+ <property name="use_stock">True</property>
2311+ </object>
2312+ </child>
2313+ <child>
2314+ <object class="GtkImageMenuItem" id="imagemenuitem_redo">
2315+ <property name="label">gtk-redo</property>
2316+ <property name="sensitive">False</property>
2317+ <property name="use_underline">True</property>
2318+ <property name="use_stock">True</property>
2319+ </object>
2320+ </child>
2321+ <child>
2322+ <object class="GtkSeparatorMenuItem" id="separatormenuitem_edit1">
2323+ <property name="sensitive">False</property>
2324+ </object>
2325+ </child>
2326+ <child>
2327+ <object class="GtkImageMenuItem" id="imagemenuitem_rotate_left">
2328+ <property name="label" translatable="yes">Rotate left</property>
2329+ <property name="visible">True</property>
2330+ <property name="use_stock">False</property>
2331+ <signal name="activate" handler="rotate_page_left"/>
2332+ </object>
2333+ </child>
2334+ <child>
2335+ <object class="GtkImageMenuItem" id="imagemenuitem_rotate_right">
2336+ <property name="label" translatable="yes">Rotate right</property>
2337+ <property name="visible">True</property>
2338+ <property name="use_stock">False</property>
2339+ <signal name="activate" handler="rotate_page_right"/>
2340+ </object>
2341+ </child>
2342+ <child>
2343+ <object class="GtkSeparatorMenuItem" id="separatormenuitem_edit2">
2344+ <property name="visible">True</property>
2345+ </object>
2346+ </child>
2347+ <child>
2348+ <object class="GtkImageMenuItem" id="imagemenuitem_crop">
2349+ <property name="label" translatable="yes">Crop</property>
2350+ <property name="visible">True</property>
2351+ <property name="use_stock">False</property>
2352+ <signal name="activate" handler="crop_page_dialog"/>
2353+ </object>
2354+ </child>
2355+ <child>
2356+ <object class="GtkImageMenuItem" id="imagemenuitem_delete">
2357+ <property name="label">gtk-delete</property>
2358+ <property name="visible">True</property>
2359+ <property name="use_underline">True</property>
2360+ <property name="use_stock">True</property>
2361+ <signal name="activate" handler="clear_selected"/>
2362+ </object>
2363+ </child>
2364+ </object>
2365+ </child>
2366+ </object>
2367+ </child>
2368+ <child>
2369+ <object class="GtkMenuItem" id="menuitem_view">
2370+ <property name="visible">True</property>
2371+ <property name="label" translatable="yes">_View</property>
2372+ <property name="use_underline">True</property>
2373+ <child type="submenu">
2374+ <object class="GtkMenu" id="menu_view">
2375+ <property name="visible">True</property>
2376+ <child>
2377+ <object class="GtkImageMenuItem" id="imagemenuitem_zoomin">
2378+ <property name="label">Zoom in</property>
2379+ <property name="visible">True</property>
2380+ <property name="use_underline">True</property>
2381+ <property name="use_stock">True</property>
2382+ <signal name="activate" handler="zoom_in"/>
2383+ </object>
2384+ </child>
2385+ <child>
2386+ <object class="GtkImageMenuItem" id="imagemenuitem_zoomout">
2387+ <property name="label">Zoom out</property>
2388+ <property name="visible">True</property>
2389+ <property name="use_underline">True</property>
2390+ <property name="use_stock">True</property>
2391+ <signal name="activate" handler="zoom_out"/>
2392+ </object>
2393+ </child>
2394+ </object>
2395+ </child>
2396+ </object>
2397+ </child>
2398+ <child>
2399+ <object class="GtkMenuItem" id="menuitem_help">
2400+ <property name="visible">True</property>
2401+ <property name="label" translatable="yes">_Help</property>
2402+ <property name="use_underline">True</property>
2403+ <child type="submenu">
2404+ <object class="GtkMenu" id="menu_help">
2405+ <property name="visible">True</property>
2406+ <child>
2407+ <object class="GtkImageMenuItem" id="imagemenuitem_about">
2408+ <property name="label">gtk-about</property>
2409+ <property name="visible">True</property>
2410+ <property name="use_underline">True</property>
2411+ <property name="use_stock">True</property>
2412+ <signal name="activate" handler="about_dialog"/>
2413+ </object>
2414+ </child>
2415+ </object>
2416+ </child>
2417+ </object>
2418+ </child>
2419+ </object>
2420+ <packing>
2421+ <property name="expand">False</property>
2422+ <property name="position">0</property>
2423+ </packing>
2424+ </child>
2425+ <child>
2426+ <object class="GtkHBox" id="hbox_toolbar">
2427+ <property name="visible">True</property>
2428+ <child>
2429+ <object class="GtkToolbar" id="toolbar">
2430+ <property name="visible">True</property>
2431+ <child>
2432+ <object class="GtkToolButton" id="toolbutton_open">
2433+ <property name="visible">True</property>
2434+ <property name="label" translatable="yes">Open</property>
2435+ <property name="use_underline">True</property>
2436+ <property name="stock_id">gtk-open</property>
2437+ <signal name="clicked" handler="on_action_add_doc_activate"/>
2438+ </object>
2439+ <packing>
2440+ <property name="expand">False</property>
2441+ <property name="homogeneous">True</property>
2442+ </packing>
2443+ </child>
2444+ <child>
2445+ <object class="GtkToolButton" id="toolbutton_save">
2446+ <property name="visible">True</property>
2447+ <property name="label" translatable="yes">Save</property>
2448+ <property name="use_underline">True</property>
2449+ <property name="stock_id">gtk-save</property>
2450+ </object>
2451+ <packing>
2452+ <property name="expand">False</property>
2453+ <property name="homogeneous">True</property>
2454+ </packing>
2455+ </child>
2456+ <child>
2457+ <object class="GtkToolButton" id="toolbutton_save_as">
2458+ <property name="visible">True</property>
2459+ <property name="label" translatable="yes">Save as</property>
2460+ <property name="use_underline">True</property>
2461+ <property name="stock_id">gtk-save-as</property>
2462+ <signal name="clicked" handler="choose_export_pdf_name"/>
2463+ </object>
2464+ <packing>
2465+ <property name="expand">False</property>
2466+ <property name="homogeneous">True</property>
2467+ </packing>
2468+ </child>
2469+ <child>
2470+ <object class="GtkSeparatorToolItem" id="toolbutton_separator1">
2471+ <property name="visible">True</property>
2472+ </object>
2473+ <packing>
2474+ <property name="expand">False</property>
2475+ <property name="homogeneous">True</property>
2476+ </packing>
2477+ </child>
2478+ <child>
2479+ <object class="GtkToolButton" id="toolbutton_zoom_in">
2480+ <property name="visible">True</property>
2481+ <property name="label" translatable="yes">Zoom in</property>
2482+ <property name="use_underline">True</property>
2483+ <property name="stock_id">gtk-zoom-in</property>
2484+ <signal name="clicked" handler="zoom_in"/>
2485+ </object>
2486+ <packing>
2487+ <property name="expand">False</property>
2488+ <property name="homogeneous">True</property>
2489+ </packing>
2490+ </child>
2491+ <child>
2492+ <object class="GtkToolButton" id="toolbutton_zoom_out">
2493+ <property name="visible">True</property>
2494+ <property name="label" translatable="yes">Zoom out</property>
2495+ <property name="use_underline">True</property>
2496+ <property name="stock_id">gtk-zoom-out</property>
2497+ <signal name="clicked" handler="zoom_out"/>
2498+ </object>
2499+ <packing>
2500+ <property name="expand">False</property>
2501+ <property name="homogeneous">True</property>
2502+ </packing>
2503+ </child>
2504+ <child>
2505+ <object class="GtkSeparatorToolItem" id="toolbutton_separator2">
2506+ <property name="visible">True</property>
2507+ </object>
2508+ <packing>
2509+ <property name="expand">False</property>
2510+ <property name="homogeneous">True</property>
2511+ </packing>
2512+ </child>
2513+ <child>
2514+ <object class="GtkToolButton" id="toolbutton_rotate_left">
2515+ <property name="visible">True</property>
2516+ <property name="label" translatable="yes">Rotate left</property>
2517+ <property name="use_underline">True</property>
2518+ <property name="icon_name">object-rotate-left</property>
2519+ <signal name="clicked" handler="rotate_page_left"/>
2520+ </object>
2521+ <packing>
2522+ <property name="expand">False</property>
2523+ <property name="homogeneous">True</property>
2524+ </packing>
2525+ </child>
2526+ <child>
2527+ <object class="GtkToolButton" id="toolbutton_rotate_right">
2528+ <property name="visible">True</property>
2529+ <property name="label" translatable="yes">Rotate right</property>
2530+ <property name="use_underline">True</property>
2531+ <property name="icon_name">object-rotate-right</property>
2532+ <signal name="clicked" handler="rotate_page_right"/>
2533+ </object>
2534+ <packing>
2535+ <property name="expand">False</property>
2536+ <property name="homogeneous">True</property>
2537+ </packing>
2538+ </child>
2539+ <child>
2540+ <object class="GtkToolButton" id="toolbutton_delete">
2541+ <property name="visible">True</property>
2542+ <property name="label" translatable="yes">Delete</property>
2543+ <property name="use_underline">True</property>
2544+ <property name="stock_id">gtk-delete</property>
2545+ <signal name="clicked" handler="clear_selected"/>
2546+ </object>
2547+ <packing>
2548+ <property name="expand">False</property>
2549+ <property name="homogeneous">True</property>
2550+ </packing>
2551+ </child>
2552+ </object>
2553+ <packing>
2554+ <property name="position">0</property>
2555+ </packing>
2556+ </child>
2557+ </object>
2558+ <packing>
2559+ <property name="expand">False</property>
2560+ <property name="position">1</property>
2561+ </packing>
2562+ </child>
2563+ <child>
2564+ <object class="GtkScrolledWindow" id="scrolledwindow">
2565+ <property name="visible">True</property>
2566+ <property name="can_focus">True</property>
2567+ <property name="hscrollbar_policy">never</property>
2568+ <property name="vscrollbar_policy">automatic</property>
2569+ <child>
2570+ <placeholder/>
2571+ </child>
2572+ </object>
2573+ <packing>
2574+ <property name="position">2</property>
2575+ </packing>
2576+ </child>
2577+ <child>
2578+ <object class="GtkProgressBar" id="progressbar">
2579+ <property name="height_request">20</property>
2580+ <property name="visible">True</property>
2581+ </object>
2582+ <packing>
2583+ <property name="expand">False</property>
2584+ <property name="padding">5</property>
2585+ <property name="position">3</property>
2586+ </packing>
2587+ </child>
2588+ </object>
2589+ </child>
2590+ </object>
2591+</interface>
2592
2593=== modified file 'debian/changelog'
2594--- debian/changelog 2011-12-31 02:06:27 +0000
2595+++ debian/changelog 2012-02-12 00:35:23 +0000
2596@@ -1,3 +1,14 @@
2597+pdfshuffler (0.5.99~svn64-0ubuntu1) UNRELEASED; urgency=low
2598+
2599+ * New upstream snapshot. (LP: #930819)
2600+ * This version fixes a annoying bug due to the new version of poppler
2601+ * debian/patches:
2602+ - Removed the previous patch (fix-shebang: now in upstream)
2603+ - Added a patch to install pdfshuffler in /usr/share/data as required
2604+ by pdfshuffler.py
2605+
2606+ -- Matthieu Baerts (matttbe) <matttbe@gmail.com> Sun, 12 Feb 2012 01:27:07 +0100
2607+
2608 pdfshuffler (0.5.1-2build1) precise; urgency=low
2609
2610 * Rebuild to drop python2.6 dependencies.
2611
2612=== removed file 'debian/patches/fix-shebang'
2613--- debian/patches/fix-shebang 2010-01-24 17:04:08 +0000
2614+++ debian/patches/fix-shebang 1970-01-01 00:00:00 +0000
2615@@ -1,12 +0,0 @@
2616-# Description: fix shebang
2617-# Forward: not applicable
2618-# Author: Serafeim Zanikolas <serzan@hellug.gr>
2619-# Last-Update: 2010-01-24
2620---- pdfshuffler-0.3.1.orig/pdfshuffler 2009-02-08 20:59:26.000000000 +0000
2621-+++ pdfshuffler-0.3.1/pdfshuffler 2009-02-08 20:59:27.000000000 +0000
2622-@@ -1,4 +1,4 @@
2623--#!/usr/bin/env python
2624-+#!/usr/bin/python
2625- # -*- coding: utf-8 -*-
2626-
2627- """
2628
2629=== added file 'debian/patches/install_and_look_for_UI_file_in_right_dirs.patch'
2630--- debian/patches/install_and_look_for_UI_file_in_right_dirs.patch 1970-01-01 00:00:00 +0000
2631+++ debian/patches/install_and_look_for_UI_file_in_right_dirs.patch 2012-02-12 00:35:23 +0000
2632@@ -0,0 +1,31 @@
2633+# Description: Install and look for the UI file in the right dirs
2634+# Forward: yes
2635+# Author: Matthieu Baerts (matttbe) <matttbe@gmail.com>
2636+# Last-Update: 2012-02-12
2637+
2638+Index: pdfshuffler/pdfshuffler/pdfshuffler.py
2639+===================================================================
2640+--- pdfshuffler.orig/pdfshuffler/pdfshuffler.py 2012-02-12 01:15:27.115323000 +0100
2641++++ pdfshuffler/pdfshuffler/pdfshuffler.py 2012-02-12 01:24:18.630058194 +0100
2642+@@ -112,7 +112,7 @@
2643+ # Import the user interface file, trying different possible locations
2644+ ui_path = '/usr/share/pdfshuffler/pdfshuffler.ui'
2645+ if not os.path.exists(ui_path):
2646+- ui_path = '/usr/local/share/applications/pdfshuffler/pdfshuffler.ui'
2647++ ui_path = '/usr/local/share/pdfshuffler/pdfshuffler.ui'
2648+ if not os.path.exists(ui_path):
2649+ parent_dir = os.path.dirname( \
2650+ os.path.dirname(os.path.realpath(__file__)))
2651+Index: pdfshuffler/setup.py
2652+===================================================================
2653+--- pdfshuffler.orig/setup.py 2012-02-12 01:15:27.115323000 +0100
2654++++ pdfshuffler/setup.py 2012-02-12 01:24:18.630058194 +0100
2655+@@ -25,7 +25,7 @@
2656+ import re
2657+ from distutils.core import setup
2658+
2659+-data_files=[('share/applications/pdfshuffler', ['data/pdfshuffler.ui']),
2660++data_files=[('share/pdfshuffler', ['data/pdfshuffler.ui']),
2661+ ('share/applications', ['data/pdfshuffler.desktop']),
2662+ ('share/man/man1', ['doc/pdfshuffler.1']),
2663+ ('share/pixmaps', ['data/pdfshuffler.svg']),
2664
2665=== modified file 'debian/patches/series'
2666--- debian/patches/series 2009-05-16 23:26:32 +0000
2667+++ debian/patches/series 2012-02-12 00:35:23 +0000
2668@@ -1,1 +1,1 @@
2669-fix-shebang
2670+install_and_look_for_UI_file_in_right_dirs.patch
2671
2672=== modified file 'doc/pdfshuffler.1'
2673--- doc/pdfshuffler.1 2011-02-15 00:43:28 +0000
2674+++ doc/pdfshuffler.1 2012-02-12 00:35:23 +0000
2675@@ -1,4 +1,4 @@
2676-.TH PDFSHUFFLER 1 "December 2009" "version 0.5.1" "User Manuals"
2677+.TH PDFSHUFFLER 1 "December 2011" "version 0.6" "User Manuals"
2678 .SH "NAME"
2679 PDF-Shuffler \- Application for PDF Merging, Rearranging, and Splitting
2680 .SH "SYNOPSIS"
2681@@ -19,5 +19,5 @@
2682 .SH "DIAGNOSTICS"
2683 Common python and gtk diagnostics.
2684 .SH "AUTHOR"
2685-Konstantinos Poulios <poulios.konstantinos@gmail.com>
2686+Konstantinos Poulios <logari81@gmail.com>
2687
2688
2689=== modified file 'pdfshuffler'
2690--- pdfshuffler 2011-02-15 00:43:28 +0000
2691+++ pdfshuffler 1970-01-01 00:00:00 +0000
2692@@ -1,1031 +0,0 @@
2693-#!/usr/bin/python
2694-# -*- coding: utf-8 -*-
2695-
2696-"""
2697- --------------------------------------------------------------------------
2698-
2699- PDF-Shuffler 0.5.1 - pyGTK PDF Merging, Rearranging, and Splitting
2700- Copyright (C) 2008-2010 Konstantinos Poulios
2701- <https://sourceforge.net/projects/pdfshuffler>
2702-
2703- --------------------------------------------------------------------------
2704-
2705- This program is free software; you can redistribute it and/or modify
2706- it under the terms of the GNU General Public License as published by
2707- the Free Software Foundation; either version 3 of the License, or
2708- (at your option) any later version.
2709-
2710- This program is distributed in the hope that it will be useful,
2711- but WITHOUT ANY WARRANTY; without even the implied warranty of
2712- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2713- GNU General Public License for more details.
2714-
2715- You should have received a copy of the GNU General Public License along
2716- with this program; if not, write to the Free Software Foundation, Inc.,
2717- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2718-
2719- --------------------------------------------------------------------------
2720-"""
2721-
2722-import os
2723-import shutil #needed for file operations like whole directory deletion
2724-import sys #needed for proccessing of command line args
2725-import urllib #needed to parse filename information passed by DnD
2726-import threading
2727-import tempfile
2728-from copy import copy
2729-
2730-import locale #for multilanguage support
2731-import gettext
2732-gettext.install('pdfshuffler', unicode=1)
2733-
2734-try:
2735- import pygtk
2736- pygtk.require('2.0')
2737- import gtk
2738- assert gtk.gtk_version >= (2, 10, 0)
2739- assert gtk.pygtk_version >= (2, 10, 0)
2740-except AssertionError:
2741- print('You do not have the required versions of GTK+ and/or PyGTK ' +
2742- 'installed.\n\n' +
2743- 'Installed GTK+ version is ' +
2744- '.'.join([str(n) for n in gtk.gtk_version]) + '\n' +
2745- 'Required GTK+ version is 2.10.0 or higher\n\n'
2746- 'Installed PyGTK version is ' +
2747- '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' +
2748- 'Required PyGTK version is 2.10.0 or higher')
2749- sys.exit(1)
2750-except:
2751- print('PyGTK version 2.10.0 or higher is required to run this program.')
2752- print('No version of PyGTK was found on your system.')
2753- sys.exit(1)
2754-
2755-gtk.gdk.threads_init()
2756-import gobject #to use custom signals
2757-import pango #to adjust the text alignment in CellRendererText
2758-import gio #to inquire mime types information
2759-
2760-import poppler #for the rendering of pdf pages
2761-from pyPdf import PdfFileWriter, PdfFileReader
2762-
2763-class PDFshuffler:
2764- # =======================================================
2765- # All the preferences are stored here.
2766- # =======================================================
2767- prefs = {
2768- 'window width': min (700, gtk.gdk.screen_get_default().get_width() / 2 ),
2769- 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50 ),
2770- 'window x': 0,
2771- 'window y': 0,
2772- 'initial thumbnail size': 530,
2773- 'initial zoom scale': 0.25,
2774- }
2775-
2776- MODEL_ROW_INTERN = 1001
2777- MODEL_ROW_EXTERN = 1002
2778- TEXT_URI_LIST = 1003
2779- MODEL_ROW_MOTION = 1004
2780- TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN),
2781- ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN),
2782- ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)]
2783- TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST),
2784- ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)]
2785-
2786- def __init__(self):
2787- # Create the temporary directory
2788- self.tmp_dir = tempfile.mkdtemp("pdfshuffler")
2789- os.chmod(self.tmp_dir, 0700)
2790-
2791- pixmap = os.path.join(sys.prefix,'share','pixmaps','pdfshuffler.png')
2792- try:
2793- gtk.window_set_default_icon_from_file(pixmap)
2794- except:
2795- print(_('File %s does not exist') % pixmap)
2796-
2797- # Create the main window, and attach delete_event signal to terminating
2798- # the application
2799- self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
2800- self.window.set_title('PDF-Shuffler')
2801- self.window.set_border_width(0)
2802- self.window.move(self.prefs['window x'], self.prefs['window y'])
2803- self.window.set_size_request(self.prefs['window width'],
2804- self.prefs['window height'])
2805- self.window.connect('delete_event', self.close_application)
2806- self.window.show()
2807-
2808- # Create a vbox to hold the thumnails-container
2809- vbox = gtk.VBox()
2810- self.window.add(vbox)
2811-
2812- # Create a scrolled window to hold the thumbnails-container
2813- self.sw = gtk.ScrolledWindow()
2814- self.sw.set_border_width(0)
2815- self.sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
2816- self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
2817- gtk.DEST_DEFAULT_HIGHLIGHT |
2818- gtk.DEST_DEFAULT_DROP |
2819- gtk.DEST_DEFAULT_MOTION,
2820- self.TARGETS_SW,
2821- gtk.gdk.ACTION_COPY |
2822- gtk.gdk.ACTION_MOVE )
2823- self.sw.connect('drag_data_received', self.sw_dnd_received_data)
2824- self.sw.connect('button_press_event', self.sw_button_press_event)
2825- vbox.pack_start(self.sw)
2826-
2827- # Create an alignment to keep the thumbnails center-aligned
2828- align = gtk.Alignment(0.5, 0.5, 0, 0)
2829- self.sw.add_with_viewport(align)
2830-
2831- # Create ListStore model and IconView
2832- self.model = gtk.ListStore(str, # 0.Text descriptor
2833- gtk.gdk.Pixbuf, # 1.Thumbnail image
2834- int, # 2.Document number
2835- int, # 3.Page number
2836- int, # 4.Thumbnail width
2837- str, # 5.Document filename
2838- bool, # 6.Rendered
2839- int, # 7.Rotation angle
2840- float, # 8.Crop left
2841- float, # 9.Crop right
2842- float, # 10.Crop top
2843- float) # 11.Crop bottom
2844-
2845- self.zoom_scale = self.prefs['initial zoom scale']
2846- self.iv_col_width = self.prefs['initial thumbnail size']
2847-
2848- self.iconview = gtk.IconView(self.model)
2849- self.iconview.set_item_width(self.iv_col_width + 12)
2850-
2851- self.iconview.set_pixbuf_column(1)
2852-# self.cellpb = gtk.CellRendererPixbuf()
2853-# self.cellpb.set_property('follow-state', True)
2854-# self.iconview.pack_start(self.cellpb, False)
2855-# self.iconview.set_attributes(self.cellpb, pixbuf=1)
2856-
2857-# self.iconview.set_text_column(0)
2858- self.celltxt = gtk.CellRendererText()
2859- self.celltxt.set_property('width', self.iv_col_width)
2860- self.celltxt.set_property('wrap-width', self.iv_col_width)
2861- self.celltxt.set_property('alignment', pango.ALIGN_CENTER)
2862- self.iconview.pack_start(self.celltxt, False)
2863- self.iconview.set_attributes(self.celltxt, text=0)
2864-
2865- self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE)
2866- self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
2867- self.TARGETS_IV,
2868- gtk.gdk.ACTION_COPY |
2869- gtk.gdk.ACTION_MOVE )
2870- self.iconview.enable_model_drag_dest(self.TARGETS_IV,
2871- gtk.gdk.ACTION_DEFAULT)
2872- self.iconview.connect('drag_begin', self.iv_drag_begin)
2873- self.iconview.connect('drag_data_get', self.iv_dnd_get_data)
2874- self.iconview.connect('drag_data_received', self.iv_dnd_received_data)
2875- self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete)
2876- self.iconview.connect('drag_motion', self.iv_dnd_motion)
2877- self.iconview.connect('drag_leave', self.iv_dnd_leave_end)
2878- self.iconview.connect('drag_end', self.iv_dnd_leave_end)
2879- self.iconview.connect('button_press_event', self.iv_button_press_event)
2880- self.iv_auto_scroll_direction = 0
2881-
2882- style = self.iconview.get_style().copy()
2883- style_sw = self.sw.get_style()
2884- for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE):
2885- style.base[state] = style_sw.bg[gtk.STATE_NORMAL]
2886- self.iconview.set_style(style)
2887-
2888- align.add(self.iconview)
2889-
2890- # Create a horizontal box to hold the buttons
2891- hbox = gtk.HBox()
2892- vbox.pack_start(hbox, expand=False, fill=False)
2893-
2894- # Create buttons
2895- self.button_exit = gtk.Button(stock=gtk.STOCK_QUIT)
2896- self.button_exit.connect('clicked', self.close_application)
2897- hbox.pack_start(self.button_exit, expand=True, fill=True, padding=20)
2898-
2899- self.button_del = gtk.Button(_('Delete Page(s)'))
2900- image = gtk.Image()
2901- image.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_BUTTON)
2902- self.button_del.set_image(image)
2903- self.button_del.connect('clicked', self.clear_selected)
2904- hbox.pack_start(self.button_del, expand=True, fill=True, padding=20)
2905-
2906- self.button_import = gtk.Button(_('Import pdf'))
2907- image = gtk.Image()
2908- image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON)
2909- self.button_import.set_image(image)
2910- self.button_import.connect('clicked', self.on_action_add_doc_activate)
2911- hbox.pack_start(self.button_import, expand=True, fill=True, padding=20)
2912-
2913- self.button_export = gtk.Button(_('Export pdf'))
2914- image = gtk.Image()
2915- image.set_from_stock(gtk.STOCK_SAVE_AS, gtk.ICON_SIZE_BUTTON)
2916- self.button_export.set_image(image)
2917- self.button_export.connect('clicked', self.choose_export_pdf_name)
2918- hbox.pack_start(self.button_export, expand=True, fill=True, padding=20)
2919-
2920- self.button_export = gtk.Button(_('About'))
2921- image = gtk.Image()
2922- image.set_from_stock(gtk.STOCK_ABOUT, gtk.ICON_SIZE_BUTTON)
2923- self.button_export.set_image(image)
2924- self.button_export.connect('clicked', self.about_dialog)
2925- hbox.pack_start(self.button_export, expand=True, fill=True, padding=20)
2926-
2927- # Define window callback function and show window
2928- self.window.connect('size_allocate', self.on_window_size_request) # resize
2929- self.window.connect('key_press_event', self.on_keypress_event ) # keypress
2930- self.window.show_all()
2931-
2932- #Creating the popup menu
2933- self.popup = gtk.Menu()
2934- popup_rotate_right = gtk.MenuItem(_('Rotate Page(s) Clockwise'))
2935- popup_rotate_left = gtk.MenuItem(_('Rotate Page(s) Counterclockwise'))
2936- popup_crop = gtk.MenuItem(_('Crop Page(s)'))
2937- popup_delete = gtk.MenuItem(_('Delete Page(s)'))
2938- popup_rotate_right.connect('activate', self.rotate_page_right)
2939- popup_rotate_left.connect('activate', self.rotate_page_left)
2940- popup_crop.connect('activate', self.crop_page_dialog)
2941- popup_delete.connect('activate', self.clear_selected)
2942- popup_rotate_right.show()
2943- popup_rotate_left.show()
2944- popup_crop.show()
2945- popup_delete.show()
2946- self.popup.append(popup_rotate_right)
2947- self.popup.append(popup_rotate_left)
2948- self.popup.append(popup_crop)
2949- self.popup.append(popup_delete)
2950-
2951- # Initializing variables
2952- self.export_directory = os.getenv('HOME')
2953- self.import_directory = os.getenv('HOME')
2954- self.nfile = 0
2955- self.iv_auto_scroll_timer = None
2956- self.pdfqueue = []
2957-
2958- gobject.type_register(PDF_Renderer)
2959- gobject.signal_new('reset_iv_width', PDF_Renderer,
2960- gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
2961- self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue,
2962- self.zoom_scale, self.iv_col_width)
2963- self.rendering_thread.connect('reset_iv_width', self.reset_iv_width)
2964- self.rendering_thread.start()
2965-
2966- # Importing documents passed as command line arguments
2967- for filename in sys.argv[1:]:
2968- self.add_pdf_pages(filename)
2969-
2970- # =======================================================
2971- def render(self):
2972- if self.rendering_thread.paused:
2973- self.rendering_thread.paused = False
2974- self.rendering_thread.evnt.set()
2975- self.rendering_thread.evnt.clear()
2976-
2977- # =======================================================
2978- def on_window_size_request(self, window, event):
2979- """Main Window resize - workaround for autosetting of
2980- iconview cols no."""
2981-
2982- #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152
2983- col_num = 9 * window.get_size()[0] / (10 * (self.iv_col_width + 12))
2984- self.iconview.set_columns(col_num)
2985-
2986- # =======================================================
2987- def reset_iv_width(self, renderer=None):
2988- """Reconfigures the width of the iconview columns"""
2989-
2990- max_w = max(row[4] for row in self.model)
2991- if max_w != self.iv_col_width:
2992- self.iv_col_width = max_w
2993- self.celltxt.set_property('width', self.iv_col_width)
2994- self.celltxt.set_property('wrap-width', self.iv_col_width)
2995- self.iconview.set_item_width(self.iv_col_width + 12) #-1)
2996- self.on_window_size_request(self.window, None)
2997-
2998- # =======================================================
2999- def on_keypress_event(self, widget, event):
3000- """Keypress events in Main Window"""
3001-
3002- #keyname = gtk.gdk.keyval_name(event.keyval)
3003- if event.keyval == 65535: # Delete keystroke
3004- self.clear_selected()
3005-
3006- # =======================================================
3007- def close_application(self, widget, event=None, data=None):
3008- """Termination"""
3009-
3010- #gtk.gdk.threads_leave()
3011- self.rendering_thread.quit = True
3012- #gtk.gdk.threads_enter()
3013- if self.rendering_thread.paused == True:
3014- self.rendering_thread.evnt.set()
3015- self.rendering_thread.evnt.clear()
3016- if os.path.isdir(self.tmp_dir):
3017- shutil.rmtree(self.tmp_dir)
3018- if gtk.main_level():
3019- gtk.main_quit()
3020- else:
3021- sys.exit(0)
3022- return False
3023-
3024- # =======================================================
3025- def add_pdf_pages(self, filename,
3026- firstpage=None, lastpage=None,
3027- angle=0, crop=[0.,0.,0.,0.] ):
3028- """Add pages of a pdf document to the model"""
3029-
3030- res = False
3031- # Check if the document has already been loaded
3032- pdfdoc = None
3033- for it_pdfdoc in self.pdfqueue:
3034- if os.path.isfile(it_pdfdoc.filename) and \
3035- os.path.samefile(filename, it_pdfdoc.filename) and \
3036- os.path.getmtime(filename) is it_pdfdoc.mtime:
3037- pdfdoc = it_pdfdoc
3038- break
3039-
3040- if not pdfdoc:
3041- pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir)
3042- self.import_directory = os.path.split(filename)[0]
3043- self.export_directory = self.import_directory
3044- if pdfdoc.nfile != 0 and pdfdoc != []:
3045- self.nfile = pdfdoc.nfile
3046- self.pdfqueue.append(pdfdoc)
3047- else:
3048- return res
3049-
3050- n_start = 1
3051- n_end = pdfdoc.npage
3052- if firstpage:
3053- n_start = min(n_end, max(1, firstpage))
3054- if lastpage:
3055- n_end = max(n_start, min(n_end, lastpage))
3056-
3057- for npage in range(n_start, n_end + 1):
3058- descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)])
3059- width = self.iv_col_width
3060- thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
3061- 8, width, width)
3062- self.model.append((descriptor, # 0
3063- thumbnail, # 1
3064- pdfdoc.nfile, # 2
3065- npage, # 3
3066- width, # 4
3067- pdfdoc.filename, # 5
3068- False, # 6
3069- angle, # 7
3070- crop[0],crop[1], # 8-9
3071- crop[2],crop[3] )) # 10-11
3072- res = True
3073-
3074- if res:
3075- self.render()
3076- return res
3077-
3078- # =======================================================
3079- def choose_export_pdf_name(self, widget=None):
3080- """Handles choosing a name for exporting """
3081-
3082- chooser = gtk.FileChooserDialog(title=_('Export ...'),
3083- action=gtk.FILE_CHOOSER_ACTION_SAVE,
3084- buttons=(gtk.STOCK_CANCEL,
3085- gtk.RESPONSE_CANCEL,
3086- gtk.STOCK_SAVE,
3087- gtk.RESPONSE_OK))
3088- chooser.set_do_overwrite_confirmation(True)
3089- chooser.set_current_folder(self.export_directory)
3090- filter_pdf = gtk.FileFilter()
3091- filter_pdf.set_name(_('PDF files'))
3092- filter_pdf.add_mime_type('application/pdf')
3093- chooser.add_filter(filter_pdf)
3094-
3095- filter_all = gtk.FileFilter()
3096- filter_all.set_name(_('All files'))
3097- filter_all.add_pattern('*')
3098- chooser.add_filter(filter_all)
3099-
3100- while True:
3101- response = chooser.run()
3102- if response == gtk.RESPONSE_OK:
3103- file_out = chooser.get_filename()
3104- (path, shortname) = os.path.split(file_out)
3105- (shortname, ext) = os.path.splitext(shortname)
3106- if ext.lower() != '.pdf':
3107- file_out = file_out + '.pdf'
3108- try:
3109- self.export_to_file(file_out)
3110- self.export_directory = path
3111- except IOError:
3112- error_msg_win = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
3113- type=gtk.MESSAGE_ERROR,
3114- message_format=_("Error writing file: %s") % file_out,
3115- buttons=gtk.BUTTONS_OK)
3116- response = error_msg_win.run()
3117- if response == gtk.RESPONSE_OK:
3118- error_msg_win.destroy()
3119- continue
3120- break
3121- chooser.destroy()
3122-
3123- # =======================================================
3124- def export_to_file(self, file_out):
3125- """Export to file"""
3126-
3127- pdf_output = PdfFileWriter()
3128- pdf_input = []
3129- for pdfdoc in self.pdfqueue:
3130- pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb'))
3131- if pdfdoc_inp.getIsEncrypted():
3132- if (pdfdoc_inp.decrypt('')!=1): # Workaround for lp:#355479
3133- print(_('File %s is encrypted.') % pdfdoc.filename)
3134- print(_('Support for such files has not been implemented yet.'))
3135- print(_('File export failed.'))
3136- return
3137- #FIXME
3138- #else
3139- # ask for password and decrypt file
3140- pdf_input.append(pdfdoc_inp)
3141-
3142- for row in self.model:
3143- # add pages from input to output document
3144- nfile = row[2]
3145- npage = row[3]
3146- current_page = copy(pdf_input[nfile-1].getPage(npage-1))
3147- angle = row[7]
3148- angle0 = current_page.get("/Rotate",0)
3149- crop = [row[8],row[9],row[10],row[11]]
3150- if angle is not 0:
3151- current_page.rotateClockwise(angle)
3152- if crop != [0.,0.,0.,0.]:
3153- rotate_times = ( ( ( angle + angle0 ) % 360 + 45 ) / 90 ) % 4
3154- crop_init = crop
3155- if rotate_times is not 0:
3156- perm = [0,2,1,3]
3157- for it in range(rotate_times):
3158- perm.append(perm.pop(0))
3159- perm.insert(1,perm.pop(2))
3160- crop = [crop_init[perm[side]] for side in range(4)]
3161- #(x1, y1) = current_page.cropBox.lowerLeft
3162- #(x2, y2) = current_page.cropBox.upperRight
3163- (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft]
3164- (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight]
3165- x1_new = int( x1 + (x2-x1) * crop[0] )
3166- x2_new = int( x2 - (x2-x1) * crop[1] )
3167- y1_new = int( y1 + (y2-y1) * crop[3] )
3168- y2_new = int( y2 - (y2-y1) * crop[2] )
3169- #current_page.cropBox.lowerLeft = (x1_new, y1_new)
3170- #current_page.cropBox.upperRight = (x2_new, y2_new)
3171- current_page.mediaBox.lowerLeft = (x1_new, y1_new)
3172- current_page.mediaBox.upperRight = (x2_new, y2_new)
3173-
3174- pdf_output.addPage(current_page)
3175-
3176- # finally, write "output" to document-output.pdf
3177- print(_('exporting to:'), file_out)
3178- pdf_output.write(file(file_out, 'wb'))
3179-
3180- # =======================================================
3181- def on_action_add_doc_activate(self, widget, data=None):
3182- """Import doc"""
3183-
3184- chooser = gtk.FileChooserDialog(title=_('Import...'),
3185- action=gtk.FILE_CHOOSER_ACTION_OPEN,
3186- buttons=(gtk.STOCK_CANCEL,
3187- gtk.RESPONSE_CANCEL,
3188- gtk.STOCK_OPEN,
3189- gtk.RESPONSE_OK))
3190- chooser.set_current_folder(self.import_directory)
3191- chooser.set_select_multiple(True)
3192-
3193- filter_all = gtk.FileFilter()
3194- filter_all.set_name(_('All files'))
3195- filter_all.add_pattern('*')
3196- chooser.add_filter(filter_all)
3197-
3198- filter_pdf = gtk.FileFilter()
3199- filter_pdf.set_name(_('PDF files'))
3200- filter_pdf.add_mime_type('application/pdf')
3201- chooser.add_filter(filter_pdf)
3202- chooser.set_filter(filter_pdf)
3203-
3204- response = chooser.run()
3205- if response == gtk.RESPONSE_OK:
3206- for filename in chooser.get_filenames():
3207- if os.path.isfile(filename):
3208- # FIXME
3209- f = gio.File(filename)
3210- f_info = f.query_info('standard::content-type')
3211- mime_type = f_info.get_content_type()
3212- if mime_type == 'application/pdf':
3213- self.add_pdf_pages(filename)
3214- elif mime_type[:34] == 'application/vnd.oasis.opendocument':
3215- print(_('OpenDocument not supported yet!'))
3216- elif mime_type[:5] == 'image':
3217- print(_('Image file not supported yet!'))
3218- else:
3219- print(_('File type not supported!'))
3220- else:
3221- print(_('File %s does not exist') % filename)
3222- elif response == gtk.RESPONSE_CANCEL:
3223- print(_('Closed, no files selected'))
3224- chooser.destroy()
3225-
3226- # =======================================================
3227- def clear_selected(self, button=None):
3228- """Removes the selected Element in the IconView"""
3229-
3230- model = self.iconview.get_model()
3231- selection = self.iconview.get_selected_items()
3232- if selection:
3233- selection.sort(reverse=True)
3234- for path in selection:
3235- iter = model.get_iter(path)
3236- model.remove(iter)
3237- path = selection[-1]
3238- self.iconview.select_path(path)
3239- if not self.iconview.path_is_selected(path):
3240- if len(model) > 0: # select the last row
3241- row = model[-1]
3242- path = row.path
3243- self.iconview.select_path(path)
3244- self.iconview.grab_focus()
3245-
3246- # =======================================================
3247- def iv_drag_begin(self, iconview, context):
3248- """Sets custom icon on drag begin for multiple items selected"""
3249-
3250- if len(iconview.get_selected_items()) > 1:
3251- iconview.stop_emission('drag_begin')
3252- context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0)
3253-
3254- # =======================================================
3255- def iv_dnd_get_data(self, iconview, context,
3256- selection_data, target_id, etime):
3257- """Handles requests for data by drag and drop in iconview"""
3258-
3259- model = iconview.get_model()
3260- selection = self.iconview.get_selected_items()
3261- selection.sort(key=lambda x: x[0])
3262- data = []
3263- for path in selection:
3264- if selection_data.target == 'MODEL_ROW_INTERN':
3265- data.append( str(path[0]) )
3266- elif selection_data.target == 'MODEL_ROW_EXTERN':
3267- iter = model.get_iter(path)
3268- nfile, npage, angle = model.get(iter, 2, 3, 7)
3269- crop = model.get(iter, 8, 9, 10, 11)
3270- pdfdoc = self.pdfqueue[nfile - 1]
3271- data.append('\n'.join([pdfdoc.filename,
3272- str(npage),
3273- str(angle) ] +
3274- [str(side) for side in crop] ) )
3275- if data:
3276- data = '\n;\n'.join(data)
3277- selection_data.set(selection_data.target, 8, data)
3278-
3279- # =======================================================
3280- def iv_dnd_received_data(self, iconview, context, x, y,
3281- selection_data, target_id, etime):
3282- """Handles received data by drag and drop in iconview"""
3283-
3284- model = iconview.get_model()
3285- data = selection_data.data
3286- if data:
3287- data = data.split('\n;\n')
3288- drop_info = iconview.get_dest_item_at_pos(x, y)
3289- iter_to = None
3290- if drop_info:
3291- path, position = drop_info
3292- ref_to = gtk.TreeRowReference(model,path)
3293- else:
3294- position = gtk.ICON_VIEW_DROP_RIGHT
3295- if len(model) > 0: #find the iterator of the last row
3296- row = model[-1]
3297- path = row.path
3298- ref_to = gtk.TreeRowReference(model,path)
3299- if ref_to:
3300- before = ( position == gtk.ICON_VIEW_DROP_LEFT
3301- or position == gtk.ICON_VIEW_DROP_ABOVE)
3302- #if target_id == self.MODEL_ROW_INTERN:
3303- if selection_data.target == 'MODEL_ROW_INTERN':
3304- if before:
3305- data.sort(key=int)
3306- else:
3307- data.sort(key=int,reverse=True)
3308- ref_from_list = [gtk.TreeRowReference(model,path)
3309- for path in data]
3310- for ref_from in ref_from_list:
3311- path = ref_to.get_path()
3312- iter_to = model.get_iter(path)
3313- path = ref_from.get_path()
3314- iter_from = model.get_iter(path)
3315- row = model[iter_from]
3316- if before:
3317- model.insert_before(iter_to, row)
3318- else:
3319- model.insert_after(iter_to, row)
3320- if context.action == gtk.gdk.ACTION_MOVE:
3321- for ref_from in ref_from_list:
3322- path = ref_from.get_path()
3323- iter_from = model.get_iter(path)
3324- model.remove(iter_from)
3325-
3326- #elif target_id == self.MODEL_ROW_EXTERN:
3327- elif selection_data.target == 'MODEL_ROW_EXTERN':
3328- if not before:
3329- data.reverse()
3330- while data:
3331- tmp = data.pop(0).split('\n')
3332- filename = tmp[0]
3333- npage, angle = [int(k) for k in tmp[1:3]]
3334- crop = [float(side) for side in tmp[3:7]]
3335- if self.add_pdf_pages(filename, npage, npage,
3336- angle, crop ):
3337- if len(model) > 0:
3338- path = ref_to.get_path()
3339- iter_to = model.get_iter(path)
3340- row = model[-1] #the last row
3341- path = row.path
3342- iter_from = model.get_iter(path)
3343- if before:
3344- model.move_before(iter_from, iter_to)
3345- else:
3346- model.move_after(iter_from, iter_to)
3347- if context.action == gtk.gdk.ACTION_MOVE:
3348- context.finish(True, True, etime)
3349-
3350- # =======================================================
3351- def iv_dnd_data_delete(self, widget, context):
3352- """Deletes dnd items after a successful move operation"""
3353-
3354- model = self.iconview.get_model()
3355- selection = self.iconview.get_selected_items()
3356- ref_del_list = [gtk.TreeRowReference(model,path) for path in selection]
3357- for ref_del in ref_del_list:
3358- path = ref_del.get_path()
3359- iter = model.get_iter(path)
3360- model.remove(iter)
3361-
3362- # =======================================================
3363- def iv_dnd_motion(self, iconview, context, x, y, etime):
3364- """Handles the drag-motion signal in order to auto-scroll the view"""
3365-
3366- autoscroll_area = 40
3367- sw_vadj = self.sw.get_vadjustment()
3368- sw_height = self.sw.get_allocation().height
3369- if y -sw_vadj.get_value() < autoscroll_area:
3370- if not self.iv_auto_scroll_timer:
3371- self.iv_auto_scroll_direction = gtk.DIR_UP
3372- self.iv_auto_scroll_timer = gobject.timeout_add(150,
3373- self.iv_auto_scroll)
3374- elif y -sw_vadj.get_value() > sw_height - autoscroll_area:
3375- if not self.iv_auto_scroll_timer:
3376- self.iv_auto_scroll_direction = gtk.DIR_DOWN
3377- self.iv_auto_scroll_timer = gobject.timeout_add(150,
3378- self.iv_auto_scroll)
3379- elif self.iv_auto_scroll_timer:
3380- gobject.source_remove(self.iv_auto_scroll_timer)
3381- self.iv_auto_scroll_timer = None
3382-
3383- # =======================================================
3384- def iv_dnd_leave_end(self, widget, context, ignored=None):
3385- """Ends the auto-scroll during DND"""
3386-
3387- if self.iv_auto_scroll_timer:
3388- gobject.source_remove(self.iv_auto_scroll_timer)
3389- self.iv_auto_scroll_timer = None
3390-
3391- # =======================================================
3392- def iv_auto_scroll(self):
3393- """Timeout routine for auto-scroll"""
3394-
3395- sw_vadj = self.sw.get_vadjustment()
3396- sw_vpos = sw_vadj.get_value()
3397- if self.iv_auto_scroll_direction == gtk.DIR_UP:
3398- sw_vpos -= sw_vadj.step_increment
3399- sw_vadj.set_value( max(sw_vpos, sw_vadj.lower) )
3400- elif self.iv_auto_scroll_direction == gtk.DIR_DOWN:
3401- sw_vpos += sw_vadj.step_increment
3402- sw_vadj.set_value( min(sw_vpos, sw_vadj.upper - sw_vadj.page_size) )
3403- return True #call me again
3404-
3405- # =======================================================
3406- def iv_button_press_event(self, iconview, event):
3407- """Manages mouse clicks on the iconview"""
3408-
3409- if event.button == 3:
3410- x = int(event.x)
3411- y = int(event.y)
3412- time = event.time
3413- path = iconview.get_path_at_pos(x, y)
3414- selection = iconview.get_selected_items()
3415- if path:
3416- if path not in selection:
3417- iconview.unselect_all()
3418- iconview.select_path(path)
3419- iconview.grab_focus()
3420- self.popup.popup( None, None, None, event.button, time)
3421- return 1
3422-
3423- # =======================================================
3424- def sw_dnd_received_data(self, scrolledwindow, context, x, y,
3425- selection_data, target_id, etime):
3426- """Handles received data by drag and drop in scrolledwindow"""
3427-
3428- data = selection_data.data
3429- if target_id == self.MODEL_ROW_EXTERN:
3430- self.model
3431- if data:
3432- data = data.split('\n;\n')
3433- while data:
3434- tmp = data.pop(0).split('\n')
3435- filename = tmp[0]
3436- npage, angle = [int(k) for k in tmp[1:3]]
3437- crop = [float(side) for side in tmp[3:7]]
3438- if self.add_pdf_pages(filename, npage, npage, angle, crop):
3439- if context.action == gtk.gdk.ACTION_MOVE:
3440- context.finish(True, True, etime)
3441- elif target_id == self.TEXT_URI_LIST:
3442- uri = data.strip()
3443- uri_splitted = uri.split() # we may have more than one file dropped
3444- for uri in uri_splitted:
3445- filename = self.get_file_path_from_dnd_dropped_uri(uri)
3446- if os.path.isfile(filename): # is it file?
3447- self.add_pdf_pages(filename)
3448-
3449- # =======================================================
3450- def sw_button_press_event(self, scrolledwindow, event):
3451- """Unselects all items in iconview on mouse click in scrolledwindow"""
3452-
3453- if event.button == 1:
3454- self.iconview.unselect_all()
3455-
3456- # =======================================================
3457- def get_file_path_from_dnd_dropped_uri(self, uri):
3458- """Extracts the path from an uri"""
3459-
3460- path = urllib.url2pathname(uri) # escape special chars
3461- path = path.strip('\r\n\x00') # remove \r\n and NULL
3462-
3463- # get the path to file
3464- if path.startswith('file:\\\\\\'): # windows
3465- path = path[8:] # 8 is len('file:///')
3466- elif path.startswith('file://'): # nautilus, rox
3467- path = path[7:] # 7 is len('file://')
3468- elif path.startswith('file:'): # xffm
3469- path = path[5:] # 5 is len('file:')
3470- return path
3471-
3472- # =======================================================
3473- def rotate_page_right(self, widget, data=None):
3474- self.rotate_page(90)
3475-
3476- def rotate_page_left(self, widget, data=None):
3477- self.rotate_page(-90)
3478-
3479- def rotate_page(self, angle):
3480- """Rotates the selected page in the IconView"""
3481-
3482- model = self.iconview.get_model()
3483- selection = self.iconview.get_selected_items()
3484- for path in selection:
3485- iter = model.get_iter(path)
3486- nfile = model.get_value(iter, 2)
3487- npage = model.get_value(iter, 3)
3488-
3489- rotate_times = ( ( (-angle) % 360 + 45 ) / 90 ) % 4
3490- crop = [0.,0.,0.,0.]
3491- if rotate_times is not 0:
3492- perm = [0,2,1,3]
3493- for it in range(rotate_times):
3494- perm.append(perm.pop(0))
3495- perm.insert(1,perm.pop(2))
3496- crop = [model.get_value(iter, 8 + perm[side]) for side in range(4)]
3497- for side in range(4):
3498- model.set_value(iter, 8 + side, crop[side])
3499-
3500- new_angle = model.get_value(iter, 7) + angle
3501- model.set_value(iter, 7, new_angle)
3502- model.set_value(iter, 6, False) #rendering request
3503-
3504- self.render()
3505-
3506- # =======================================================
3507- def crop_page_dialog(self, widget):
3508- """Opens a dialog box to define margins for page cropping"""
3509-
3510- sides = ('L', 'R', 'T', 'B')
3511- side_names = {'L':_('Left'), 'R':_('Right'),
3512- 'T':_('Top'), 'B':_('Bottom') }
3513- opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' }
3514-
3515- def set_crop_value(spinbutton, side):
3516- opp_side = opposite_sides[side]
3517- pos = sides.index(opp_side)
3518- adj = spin_list[pos].get_adjustment()
3519- adj.set_upper(99.0 - spinbutton.get_value())
3520-
3521- model = self.iconview.get_model()
3522- selection = self.iconview.get_selected_items()
3523-
3524- crop = [0.,0.,0.,0.]
3525- if selection:
3526- path = selection[0]
3527- pos = model.get_iter(path)
3528- crop = [model.get_value(pos, 8 + side) for side in range(4)]
3529-
3530- dialog = gtk.Dialog(title=(_('Crop Selected Page(s)')),
3531- parent=self.window,
3532- flags=gtk.DIALOG_MODAL,
3533- buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
3534- gtk.STOCK_OK, gtk.RESPONSE_OK))
3535- dialog.set_size_request(340, 250)
3536- dialog.set_default_response(gtk.RESPONSE_OK)
3537-
3538- frame = gtk.Frame(_('Crop Margins'))
3539- dialog.vbox.pack_start(frame, False, False, 20)
3540-
3541- vbox = gtk.VBox(False, 0)
3542- frame.add(vbox)
3543-
3544- spin_list = []
3545- units = 2 * [_('% of width')] + 2 * [_('% of height')]
3546- for side in sides:
3547- hbox = gtk.HBox(True, 0)
3548- vbox.pack_start(hbox, False, False, 5)
3549-
3550- label = gtk.Label(side_names[side])
3551- label.set_alignment(0, 0.0)
3552- hbox.pack_start(label, True, True, 20)
3553-
3554- adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0)
3555- spin = gtk.SpinButton(adj, 0, 1)
3556- spin.set_activates_default(True)
3557- spin.connect('value-changed', set_crop_value, side)
3558- spin_list.append(spin)
3559- hbox.pack_start(spin, False, False, 30)
3560-
3561- label = gtk.Label(units.pop(0))
3562- label.set_alignment(0, 0.0)
3563- hbox.pack_start(label, True, True, 0)
3564-
3565- dialog.show_all()
3566- result = dialog.run()
3567-
3568- if result == gtk.RESPONSE_OK:
3569- crop = []
3570- for spin in spin_list:
3571- crop.append( spin.get_value()/100. )
3572- for path in selection:
3573- pos = model.get_iter(path)
3574- for it in range(4):
3575- model.set_value(pos, 8 + it, crop[it])
3576- model.set_value(pos, 6, False) #rendering request
3577- self.render()
3578- elif result == gtk.RESPONSE_CANCEL:
3579- print(_('Dialog closed'))
3580- dialog.destroy()
3581-
3582- # =======================================================
3583- def about_dialog(self, widget, data=None):
3584- about_dialog = gtk.AboutDialog()
3585- try:
3586- about_dialog.set_transient_for(self.window)
3587- about_dialog.set_modal(True)
3588- except:
3589- pass
3590- # FIXME
3591- about_dialog.set_name('PDF-Shuffler')
3592- about_dialog.set_version('0.5.1')
3593- about_dialog.set_comments(_(
3594- 'PDF-Shuffler is a simple pyGTK utility which lets you merge, \
3595-split and rearrange PDF documents. You can also rotate and crop individual \
3596-pages of a pdf document.'))
3597- about_dialog.set_authors(['Konstantinos Poulios',])
3598- about_dialog.set_website_label('http://pdfshuffler.sourceforge.net/')
3599- about_dialog.set_logo_icon_name('pdfshuffler')
3600- about_dialog.connect('response', lambda w, a: about_dialog.destroy())
3601- about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy())
3602- about_dialog.show_all()
3603-
3604-# =======================================================
3605-class PDF_Doc:
3606- """Class handling pdf documents"""
3607-
3608- def __init__(self, filename, nfile, tmp_dir):
3609-
3610- self.filename = os.path.abspath(filename)
3611- (self.path, self.shortname) = os.path.split(self.filename)
3612- (self.shortname, self.ext) = os.path.splitext(self.shortname)
3613- f = gio.File(filename)
3614- mime_type = f.query_info('standard::content-type').get_content_type()
3615- if mime_type == 'application/pdf':
3616- self.nfile = nfile + 1
3617- self.mtime = os.path.getmtime(filename)
3618- self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile +
3619- self.shortname + '.pdf')
3620- shutil.copy(self.filename, self.copyname)
3621- self.document = poppler.document_new_from_file("file://" + self.copyname, None)
3622- self.npage = self.document.get_n_pages()
3623- else:
3624- self.nfile = 0
3625-
3626-
3627-# =======================================================
3628-class PDF_Renderer(threading.Thread,gobject.GObject):
3629-
3630- def __init__(self, model, pdfqueue, scale=1., width=100):
3631- threading.Thread.__init__(self)
3632- gobject.GObject.__init__(self)
3633- self.model = model
3634- self.scale = scale
3635- self.default_width = width
3636- self.pdfqueue = pdfqueue
3637- self.quit = False
3638- self.evnt = threading.Event()
3639- self.paused = False
3640-
3641- def run(self):
3642- while not self.quit:
3643- rendered_all = True
3644- for row in self.model:
3645- if self.quit:
3646- break
3647- if not row[6]:
3648- rendered_all = False
3649- gtk.gdk.threads_enter()
3650- try:
3651- nfile = row[2]
3652- npage = row[3]
3653- angle = row[7]
3654- crop = [row[8],row[9],row[10],row[11]]
3655- pdfdoc = self.pdfqueue[nfile - 1]
3656- thumbnail = self.load_pdf_thumbnail(pdfdoc, npage, angle, crop)
3657- row[6] = True
3658- row[4] = thumbnail.get_width()
3659- row[1] = thumbnail
3660- finally:
3661- gtk.gdk.threads_leave()
3662- if rendered_all:
3663- if self.model.get_iter_first(): #just checking if model isn't empty
3664- self.emit('reset_iv_width')
3665- self.paused = True
3666- self.evnt.wait()
3667-
3668- # =======================================================
3669- def load_pdf_thumbnail(self, pdfdoc, npage, rotation=0, crop=[0.,0.,0.,0.]):
3670- """Create pdf pixbuf"""
3671-
3672- page = pdfdoc.document.get_page(npage-1)
3673- try:
3674- pix_w, pix_h = page.get_size()
3675- pix_w = int(pix_w * self.scale)
3676- pix_h = int(pix_h * self.scale)
3677- thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
3678- 8, pix_w , pix_h)
3679- page.render_to_pixbuf(0,0,pix_w,pix_h,self.scale,0,thumbnail)
3680- rotation = (-rotation) % 360
3681- rotation = ((rotation + 45) / 90) * 90
3682- thumbnail = thumbnail.rotate_simple(rotation)
3683- pix_w = thumbnail.get_width()
3684- pix_h = thumbnail.get_height()
3685- if crop != [0.,0.,0.,0.]:
3686- src_x = int( crop[0] * pix_w )
3687- src_y = int( crop[2] * pix_h )
3688- width = int( (1. - crop[0] - crop[1]) * pix_w )
3689- height = int( (1. - crop[2] - crop[3]) * pix_h )
3690- new_thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
3691- 8, width, height)
3692- thumbnail.copy_area(src_x, src_y, width, height,
3693- new_thumbnail, 0, 0)
3694- thumbnail = new_thumbnail
3695- pix_w = thumbnail.get_width()
3696- pix_h = thumbnail.get_height()
3697- except:
3698- pix_w = self.default_width
3699- pix_h = pix_w
3700- thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
3701- 8, pix_w, pix_h)
3702- pixbuf.fill(0xffffffff)
3703-
3704- #add border
3705- thickness = 3
3706- color = 0x000000FF
3707- canvas = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
3708- pix_w + thickness + 1,
3709- pix_h + thickness + 1)
3710- canvas.fill(color)
3711- thumbnail.copy_area(0, 0, pix_w, pix_h, canvas, 1, 1)
3712- thumbnail = canvas
3713-
3714- return thumbnail
3715-
3716-
3717-# =======================================================
3718-if __name__ == '__main__':
3719- PDFshuffler()
3720- gtk.gdk.threads_enter()
3721- gtk.main()
3722- gtk.gdk.threads_leave()
3723-
3724
3725=== added file 'pdfshuffler/__init__.py'
3726=== added file 'pdfshuffler/cairorendering.py'
3727--- pdfshuffler/cairorendering.py 1970-01-01 00:00:00 +0000
3728+++ pdfshuffler/cairorendering.py 2012-02-12 00:35:23 +0000
3729@@ -0,0 +1,168 @@
3730+#!/usr/bin/python
3731+# -*- coding: utf-8 -*-
3732+
3733+"""
3734+
3735+ PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and
3736+ modification of PDF documents.
3737+ Copyright (C) 2008-2011 Konstantinos Poulios
3738+ <https://sourceforge.net/projects/pdfshuffler>
3739+
3740+ This file is part of PdfShuffler.
3741+
3742+ PdfShuffler is free software; you can redistribute it and/or modify
3743+ it under the terms of the GNU General Public License as published by
3744+ the Free Software Foundation; either version 3 of the License, or
3745+ (at your option) any later version.
3746+
3747+ This program is distributed in the hope that it will be useful,
3748+ but WITHOUT ANY WARRANTY; without even the implied warranty of
3749+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3750+ GNU General Public License for more details.
3751+
3752+ You should have received a copy of the GNU General Public License along
3753+ with this program; if not, write to the Free Software Foundation, Inc.,
3754+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
3755+
3756+"""
3757+
3758+import gtk
3759+import gobject
3760+import cairo
3761+
3762+from math import pi as M_PI
3763+
3764+class CellRendererImage(gtk.GenericCellRenderer):
3765+ __gproperties__ = {
3766+ "image": (gobject.TYPE_OBJECT, "Image", "Image",
3767+ gobject.PARAM_READWRITE),
3768+ "width": (gobject.TYPE_DOUBLE, "Width", "Width",
3769+ 0., 1.e4, 0., gobject.PARAM_READWRITE),
3770+ "height": (gobject.TYPE_DOUBLE, "Height", "Height",
3771+ 0., 1.e4, 0., gobject.PARAM_READWRITE),
3772+ "rotation": (gobject.TYPE_INT, "Rotation", "Rotation",
3773+ 0, 360, 0, gobject.PARAM_READWRITE),
3774+ "scale": (gobject.TYPE_DOUBLE, "Scale", "Scale",
3775+ 0.01, 100., 1., gobject.PARAM_READWRITE),
3776+ "cropL": (gobject.TYPE_DOUBLE, "CropL", "CropL",
3777+ 0., 1., 0., gobject.PARAM_READWRITE),
3778+ "cropR": (gobject.TYPE_DOUBLE, "CropR", "CropR",
3779+ 0., 1., 0., gobject.PARAM_READWRITE),
3780+ "cropT": (gobject.TYPE_DOUBLE, "CropT", "CropT",
3781+ 0., 1., 0., gobject.PARAM_READWRITE),
3782+ "cropB": (gobject.TYPE_DOUBLE, "CropB", "CropB",
3783+ 0., 1., 0., gobject.PARAM_READWRITE),
3784+ }
3785+
3786+ def __init__(self):
3787+ self.__gobject_init__()
3788+ self.th1 = 2. # border thickness
3789+ self.th2 = 3. # shadow thickness
3790+
3791+ def get_geometry(self):
3792+
3793+ rotation = int(self.rotation) % 360
3794+ rotation = ((rotation + 45) / 90) * 90
3795+ if not self.image.surface:
3796+ w0 = w1 = self.width
3797+ h0 = h1 = self.height
3798+ else:
3799+ w0 = self.image.surface.get_width()
3800+ h0 = self.image.surface.get_height()
3801+ if rotation == 90 or rotation == 270:
3802+ w1, h1 = h0, w0
3803+ else:
3804+ w1, h1 = w0, h0
3805+
3806+ x = self.cropL * w1
3807+ y = self.cropT * h1
3808+
3809+ w2 = int(self.scale * (1. - self.cropL - self.cropR) * w1)
3810+ h2 = int(self.scale * (1. - self.cropT - self.cropB) * h1)
3811+
3812+ return w0,h0,w1,h1,w2,h2,rotation
3813+
3814+ def do_set_property(self, pspec, value):
3815+ setattr(self, pspec.name, value)
3816+
3817+ def do_get_property(self, pspec):
3818+ return getattr(self, pspec.name)
3819+
3820+ def on_render(self, window, widget, background_area, cell_area, \
3821+ expose_area, flags):
3822+ if not self.image.surface:
3823+ return
3824+
3825+ w0,h0,w1,h1,w2,h2,rotation = self.get_geometry()
3826+ th = int(2*self.th1+self.th2)
3827+ w = w2 + th
3828+ h = h2 + th
3829+
3830+ x = cell_area.x
3831+ y = cell_area.y
3832+ if cell_area and w > 0 and h > 0:
3833+ x += self.get_property('xalign') * \
3834+ (cell_area.width - w - self.get_property('xpad'))
3835+ y += self.get_property('yalign') * \
3836+ (cell_area.height - h - self.get_property('ypad'))
3837+
3838+ cr = window.cairo_create()
3839+ cr.translate(x,y)
3840+
3841+ x = self.cropL * w1
3842+ y = self.cropT * h1
3843+
3844+ #shadow
3845+ cr.set_source_rgb(0.5, 0.5, 0.5)
3846+ cr.rectangle(th, th, w2, h2)
3847+ cr.fill()
3848+
3849+ #border
3850+ cr.set_source_rgb(0, 0, 0)
3851+ cr.rectangle(0, 0, w2+2*self.th1, h2+2*self.th1)
3852+ cr.fill()
3853+
3854+ #image
3855+ cr.set_source_rgb(1, 1, 1)
3856+ cr.rectangle(self.th1, self.th1, w2, h2)
3857+ cr.fill_preserve()
3858+ cr.clip()
3859+
3860+ cr.translate(self.th1,self.th1)
3861+ cr.scale(self.scale, self.scale)
3862+ cr.translate(-x,-y)
3863+ if rotation > 0:
3864+ cr.translate(w1/2,h1/2)
3865+ cr.rotate(rotation * M_PI / 180)
3866+ cr.translate(-w0/2,-h0/2)
3867+
3868+ cr.set_source_surface(self.image.surface)
3869+ cr.paint()
3870+
3871+ def on_get_size(self, widget, cell_area=None):
3872+ x = y = 0
3873+ w0,h0,w1,h1,w2,h2,rotation = self.get_geometry()
3874+ th = int(2*self.th1+self.th2)
3875+ w = w2 + th
3876+ h = h2 + th
3877+
3878+ if cell_area and w > 0 and h > 0:
3879+ x = self.get_property('xalign') * \
3880+ (cell_area.width - w - self.get_property('xpad'))
3881+ y = self.get_property('yalign') * \
3882+ (cell_area.height - h - self.get_property('ypad'))
3883+ w += 2 * self.get_property('xpad')
3884+ h += 2 * self.get_property('ypad')
3885+ return int(x), int(y), w, h
3886+
3887+
3888+class CairoImage(gobject.GObject):
3889+
3890+ def __init__(self, width=0, height=0):
3891+ gobject.GObject.__init__(self)
3892+ if width > 0 and height > 0:
3893+ self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
3894+ else:
3895+ self.surface = None
3896+
3897+
3898
3899=== added file 'pdfshuffler/pdfshuffler.py'
3900--- pdfshuffler/pdfshuffler.py 1970-01-01 00:00:00 +0000
3901+++ pdfshuffler/pdfshuffler.py 2012-02-12 00:35:23 +0000
3902@@ -0,0 +1,1057 @@
3903+#!/usr/bin/python
3904+# -*- coding: utf-8 -*-
3905+
3906+"""
3907+
3908+ PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and
3909+ modification of PDF documents.
3910+ Copyright (C) 2008-2011 Konstantinos Poulios
3911+ <https://sourceforge.net/projects/pdfshuffler>
3912+
3913+ This file is part of PdfShuffler.
3914+
3915+ PdfShuffler is free software; you can redistribute it and/or modify
3916+ it under the terms of the GNU General Public License as published by
3917+ the Free Software Foundation; either version 3 of the License, or
3918+ (at your option) any later version.
3919+
3920+ This program is distributed in the hope that it will be useful,
3921+ but WITHOUT ANY WARRANTY; without even the implied warranty of
3922+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3923+ GNU General Public License for more details.
3924+
3925+ You should have received a copy of the GNU General Public License along
3926+ with this program; if not, write to the Free Software Foundation, Inc.,
3927+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
3928+
3929+"""
3930+
3931+import os
3932+import shutil #needed for file operations like whole directory deletion
3933+import sys #needed for proccessing of command line args
3934+import urllib #needed to parse filename information passed by DnD
3935+import threading
3936+import tempfile
3937+from copy import copy
3938+
3939+import locale #for multilanguage support
3940+import gettext
3941+gettext.install('pdfshuffler', unicode=1)
3942+
3943+
3944+APPNAME = 'PdfShuffler' # PDF-Shuffler, PDFShuffler, pdfshuffler
3945+VERSION = '0.6.0'
3946+WEBSITE = 'http://pdfshuffler.sourceforge.net/'
3947+LICENSE = 'GNU General Public License (GPL) Version 3.'
3948+
3949+try:
3950+ import pygtk
3951+ pygtk.require('2.0')
3952+ import gtk
3953+ assert gtk.gtk_version >= (2, 10, 0)
3954+ assert gtk.pygtk_version >= (2, 10, 0)
3955+except AssertionError:
3956+ print('You do not have the required versions of GTK+ and PyGTK ' +
3957+ 'installed.\n\n' +
3958+ 'Installed GTK+ version is ' +
3959+ '.'.join([str(n) for n in gtk.gtk_version]) + '\n' +
3960+ 'Required GTK+ version is 2.10.0 or higher\n\n'
3961+ 'Installed PyGTK version is ' +
3962+ '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' +
3963+ 'Required PyGTK version is 2.10.0 or higher')
3964+ sys.exit(1)
3965+except:
3966+ print('PyGTK version 2.10.0 or higher is required to run this program.')
3967+ print('No version of PyGTK was found on your system.')
3968+ sys.exit(1)
3969+
3970+import gobject #to use custom signals
3971+import pango #to adjust the text alignment in CellRendererText
3972+import gio #to inquire mime types information
3973+import cairo
3974+
3975+import poppler #for the rendering of pdf pages
3976+from pyPdf import PdfFileWriter, PdfFileReader
3977+
3978+from cairorendering import CellRendererImage, CairoImage
3979+gobject.type_register(CellRendererImage)
3980+
3981+import time
3982+
3983+class PdfShuffler:
3984+ prefs = {
3985+ 'window width': min(700, gtk.gdk.screen_get_default().get_width() / 2),
3986+ 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50),
3987+ 'window x': 0,
3988+ 'window y': 0,
3989+ 'initial thumbnail size': 300,
3990+ 'initial zoom level': -14,
3991+ }
3992+
3993+ MODEL_ROW_INTERN = 1001
3994+ MODEL_ROW_EXTERN = 1002
3995+ TEXT_URI_LIST = 1003
3996+ MODEL_ROW_MOTION = 1004
3997+ TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN),
3998+ ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN),
3999+ ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)]
4000+ TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST),
4001+ ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)]
4002+
4003+ def __init__(self):
4004+ # Create the temporary directory
4005+ self.tmp_dir = tempfile.mkdtemp("pdfshuffler")
4006+ os.chmod(self.tmp_dir, 0700)
4007+
4008+ icon_theme = gtk.icon_theme_get_default()
4009+ try:
4010+ gtk.window_set_default_icon(icon_theme.load_icon("pdfshuffler", 64, 0))
4011+ except:
4012+ print(_("Can't load icon. Application is not installed correctly."))
4013+
4014+ # Import the user interface file, trying different possible locations
4015+ ui_path = '/usr/share/pdfshuffler/pdfshuffler.ui'
4016+ if not os.path.exists(ui_path):
4017+ ui_path = '/usr/local/share/pdfshuffler/pdfshuffler.ui'
4018+ if not os.path.exists(ui_path):
4019+ parent_dir = os.path.dirname( \
4020+ os.path.dirname(os.path.realpath(__file__)))
4021+ ui_path = os.path.join(parent_dir, 'data', 'pdfshuffler.ui')
4022+ self.uiXML = gtk.Builder()
4023+ self.uiXML.add_from_file(ui_path)
4024+ self.uiXML.connect_signals(self)
4025+
4026+ # Create the main window, and attach delete_event signal to terminating
4027+ # the application
4028+ self.window = self.uiXML.get_object('main_window')
4029+ self.window.set_title(APPNAME)
4030+ self.window.set_border_width(0)
4031+ self.window.move(self.prefs['window x'], self.prefs['window y'])
4032+ self.window.set_default_size(self.prefs['window width'],
4033+ self.prefs['window height'])
4034+ self.window.connect('delete_event', self.close_application)
4035+ self.window.show_all()
4036+
4037+ # Create a scrolled window to hold the thumbnails-container
4038+ self.sw = self.uiXML.get_object('scrolledwindow')
4039+ self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
4040+ gtk.DEST_DEFAULT_HIGHLIGHT |
4041+ gtk.DEST_DEFAULT_DROP |
4042+ gtk.DEST_DEFAULT_MOTION,
4043+ self.TARGETS_SW,
4044+ gtk.gdk.ACTION_COPY |
4045+ gtk.gdk.ACTION_MOVE)
4046+ self.sw.connect('drag_data_received', self.sw_dnd_received_data)
4047+ self.sw.connect('button_press_event', self.sw_button_press_event)
4048+ self.sw.connect('scroll_event', self.sw_scroll_event)
4049+
4050+ # Create an alignment to keep the thumbnails center-aligned
4051+ align = gtk.Alignment(0.5, 0.5, 0, 0)
4052+ self.sw.add_with_viewport(align)
4053+
4054+ # Create ListStore model and IconView
4055+ self.model = gtk.ListStore(str, # 0.Text descriptor
4056+ CairoImage, # 1.Cached page image
4057+ int, # 2.Document number
4058+ int, # 3.Page number
4059+ float, # 4.Scale
4060+ str, # 5.Document filename
4061+ int, # 6.Rotation angle
4062+ float, # 7.Crop left
4063+ float, # 8.Crop right
4064+ float, # 9.Crop top
4065+ float, # 10.Crop bottom
4066+ int, # 11.Page width
4067+ int) # 12.Page height
4068+
4069+ self.zoom_set(self.prefs['initial zoom level'])
4070+ self.iv_col_width = self.prefs['initial thumbnail size']
4071+
4072+ self.iconview = gtk.IconView(self.model)
4073+ self.iconview.set_item_width(self.iv_col_width + 12)
4074+
4075+ self.cellthmb = CellRendererImage()
4076+ self.iconview.pack_start(self.cellthmb, False)
4077+ self.iconview.set_attributes(self.cellthmb, image=1,
4078+ scale=4, rotation=6, cropL=7, cropR=8, cropT=9, cropB=10,
4079+ width=11, height=12)
4080+
4081+# self.iconview.set_text_column(0)
4082+ self.celltxt = gtk.CellRendererText()
4083+ self.celltxt.set_property('width', self.iv_col_width)
4084+ self.celltxt.set_property('wrap-width', self.iv_col_width)
4085+ self.celltxt.set_property('alignment', pango.ALIGN_CENTER)
4086+ self.iconview.pack_start(self.celltxt, False)
4087+ self.iconview.set_attributes(self.celltxt, text=0)
4088+
4089+ self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE)
4090+ self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
4091+ self.TARGETS_IV,
4092+ gtk.gdk.ACTION_COPY |
4093+ gtk.gdk.ACTION_MOVE)
4094+ self.iconview.enable_model_drag_dest(self.TARGETS_IV,
4095+ gtk.gdk.ACTION_DEFAULT)
4096+ self.iconview.connect('drag_begin', self.iv_drag_begin)
4097+ self.iconview.connect('drag_data_get', self.iv_dnd_get_data)
4098+ self.iconview.connect('drag_data_received', self.iv_dnd_received_data)
4099+ self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete)
4100+ self.iconview.connect('drag_motion', self.iv_dnd_motion)
4101+ self.iconview.connect('drag_leave', self.iv_dnd_leave_end)
4102+ self.iconview.connect('drag_end', self.iv_dnd_leave_end)
4103+ self.iconview.connect('button_press_event', self.iv_button_press_event)
4104+ self.iv_auto_scroll_direction = 0
4105+
4106+ style = self.iconview.get_style().copy()
4107+ style_sw = self.sw.get_style()
4108+ for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE):
4109+ style.base[state] = style_sw.bg[gtk.STATE_NORMAL]
4110+ self.iconview.set_style(style)
4111+
4112+ align.add(self.iconview)
4113+
4114+ # Progress bar
4115+ self.progress_bar = self.uiXML.get_object('progressbar')
4116+ self.progress_bar_timeout_id = 0
4117+
4118+ # Define window callback function and show window
4119+ self.window.connect('size_allocate', self.on_window_size_request) # resize
4120+ self.window.connect('key_press_event', self.on_keypress_event ) # keypress
4121+ self.window.show_all()
4122+ self.progress_bar.hide_all()
4123+
4124+ # Creating the popup menu
4125+ self.popup = gtk.Menu()
4126+ popup_rotate_right = gtk.ImageMenuItem(_('_Rotate Right'))
4127+ popup_rotate_left = gtk.ImageMenuItem(_('Rotate _Left'))
4128+ popup_crop = gtk.MenuItem(_('C_rop...'))
4129+ popup_delete = gtk.ImageMenuItem(gtk.STOCK_DELETE)
4130+ popup_rotate_right.connect('activate', self.rotate_page_right)
4131+ popup_rotate_left.connect('activate', self.rotate_page_left)
4132+ popup_crop.connect('activate', self.crop_page_dialog)
4133+ popup_delete.connect('activate', self.clear_selected)
4134+ popup_rotate_right.show()
4135+ popup_rotate_left.show()
4136+ popup_crop.show()
4137+ popup_delete.show()
4138+ self.popup.append(popup_rotate_right)
4139+ self.popup.append(popup_rotate_left)
4140+ self.popup.append(popup_crop)
4141+ self.popup.append(popup_delete)
4142+
4143+ # Initializing variables
4144+ self.export_directory = os.getenv('HOME')
4145+ self.import_directory = self.export_directory
4146+ self.nfile = 0
4147+ self.iv_auto_scroll_timer = None
4148+ self.pdfqueue = []
4149+
4150+ gobject.type_register(PDF_Renderer)
4151+ gobject.signal_new('update_thumbnail', PDF_Renderer,
4152+ gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
4153+ [gobject.TYPE_INT, gobject.TYPE_PYOBJECT])
4154+ self.rendering_thread = 0
4155+
4156+ self.set_unsaved(False)
4157+
4158+ # Importing documents passed as command line arguments
4159+ for filename in sys.argv[1:]:
4160+ self.add_pdf_pages(filename)
4161+
4162+ def render(self):
4163+ if self.rendering_thread:
4164+ self.rendering_thread.quit = True
4165+ self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue)
4166+ self.rendering_thread.connect('update_thumbnail', self.update_thumbnail)
4167+ self.rendering_thread.start()
4168+
4169+ if self.progress_bar_timeout_id:
4170+ gobject.source_remove(self.progress_bar_timeout_id)
4171+ self.progress_bar_timout_id = \
4172+ gobject.timeout_add(50, self.progress_bar_timeout)
4173+
4174+ def set_unsaved(self, flag):
4175+ self.is_unsaved = flag
4176+ gobject.idle_add(self.retitle)
4177+
4178+ def retitle(self):
4179+ title = ''
4180+ if len(self.pdfqueue) == 1:
4181+ title += self.pdfqueue[0].filename
4182+ elif len(self.pdfqueue) == 0:
4183+ title += _("No document")
4184+ else:
4185+ title += _("Several documents")
4186+ if self.is_unsaved:
4187+ title += '*'
4188+ title += ' - ' + APPNAME
4189+ self.window.set_title(title)
4190+
4191+ def progress_bar_timeout(self):
4192+ cnt_finished = 0
4193+ cnt_all = 0
4194+ for row in self.model:
4195+ cnt_all += 1
4196+ if row[1].surface:
4197+ cnt_finished += 1
4198+ fraction = float(cnt_finished)/float(cnt_all)
4199+
4200+ self.progress_bar.set_fraction(fraction)
4201+ self.progress_bar.set_text(_('Rendering thumbnails... [%(i1)s/%(i2)s]')
4202+ % {'i1' : cnt_finished, 'i2' : cnt_all})
4203+ if fraction >= 0.999:
4204+ self.progress_bar.hide_all()
4205+ return False
4206+ elif not self.progress_bar.flags() & gtk.VISIBLE:
4207+ self.progress_bar.show_all()
4208+
4209+ return True
4210+
4211+ def update_thumbnail(self, object, num, thumbnail):
4212+ row = self.model[num]
4213+ row[4] = self.zoom_scale
4214+ row[1] = thumbnail
4215+
4216+ def on_window_size_request(self, window, event):
4217+ """Main Window resize - workaround for autosetting of
4218+ iconview cols no."""
4219+
4220+ #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152
4221+ col_num = 9 * window.get_size()[0] \
4222+ / (10 * (self.iv_col_width + self.iconview.get_column_spacing() * 2))
4223+ self.iconview.set_columns(col_num)
4224+
4225+ def update_geometry(self, iter):
4226+ """Recomputes the width and height of the rotated page and saves
4227+ the result in the ListStore"""
4228+
4229+ if not self.model.iter_is_valid(iter):
4230+ return
4231+
4232+ nfile, npage, rotation = self.model.get(iter, 2, 3, 6)
4233+ crop = self.model.get(iter, 7, 8, 9, 10)
4234+ page = self.pdfqueue[nfile-1].document.get_page(npage-1)
4235+ w0, h0 = page.get_size()
4236+
4237+ rotation = int(rotation) % 360
4238+ rotation = ((rotation + 45) / 90) * 90
4239+ if rotation == 90 or rotation == 270:
4240+ w1, h1 = h0, w0
4241+ else:
4242+ w1, h1 = w0, h0
4243+
4244+ self.model.set(iter, 11, w1, 12, h1)
4245+
4246+ def reset_iv_width(self, renderer=None):
4247+ """Reconfigures the width of the iconview columns"""
4248+
4249+ if not self.model.get_iter_first(): #just checking if model is empty
4250+ return
4251+
4252+ max_w = 10 + int(max(row[4]*row[11]*(1.-row[7]-row[8]) for row in self.model))
4253+ if max_w != self.iv_col_width:
4254+ self.iv_col_width = max_w
4255+ self.celltxt.set_property('width', self.iv_col_width)
4256+ self.celltxt.set_property('wrap-width', self.iv_col_width)
4257+ self.iconview.set_item_width(self.iv_col_width + 12) #-1)
4258+ self.on_window_size_request(self.window, None)
4259+
4260+ def on_keypress_event(self, widget, event):
4261+ """Keypress events in Main Window"""
4262+
4263+ #keyname = gtk.gdk.keyval_name(event.keyval)
4264+ if event.keyval == 65535: # Delete keystroke
4265+ self.clear_selected()
4266+
4267+ def close_application(self, widget, event=None, data=None):
4268+ """Termination"""
4269+
4270+ if self.rendering_thread:
4271+ self.rendering_thread.quit = True
4272+ while self.rendering_thread.isAlive():
4273+ time.sleep(0.5)
4274+
4275+ if os.path.isdir(self.tmp_dir):
4276+ shutil.rmtree(self.tmp_dir)
4277+ if gtk.main_level():
4278+ gtk.main_quit()
4279+ else:
4280+ sys.exit(0)
4281+ return False
4282+
4283+ def add_pdf_pages(self, filename,
4284+ firstpage=None, lastpage=None,
4285+ angle=0, crop=[0.,0.,0.,0.]):
4286+ """Add pages of a pdf document to the model"""
4287+
4288+ res = False
4289+ # Check if the document has already been loaded
4290+ pdfdoc = None
4291+ for it_pdfdoc in self.pdfqueue:
4292+ if os.path.isfile(it_pdfdoc.filename) and \
4293+ os.path.samefile(filename, it_pdfdoc.filename) and \
4294+ os.path.getmtime(filename) is it_pdfdoc.mtime:
4295+ pdfdoc = it_pdfdoc
4296+ break
4297+
4298+ if not pdfdoc:
4299+ pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir)
4300+ self.import_directory = os.path.split(filename)[0]
4301+ self.export_directory = self.import_directory
4302+ if pdfdoc.nfile != 0 and pdfdoc != []:
4303+ self.nfile = pdfdoc.nfile
4304+ self.pdfqueue.append(pdfdoc)
4305+ else:
4306+ return res
4307+
4308+ n_start = 1
4309+ n_end = pdfdoc.npage
4310+ if firstpage:
4311+ n_start = min(n_end, max(1, firstpage))
4312+ if lastpage:
4313+ n_end = max(n_start, min(n_end, lastpage))
4314+
4315+ for npage in range(n_start, n_end + 1):
4316+ descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)])
4317+ page = pdfdoc.document.get_page(npage-1)
4318+ w, h = page.get_size()
4319+ iter = self.model.append((descriptor, # 0
4320+ CairoImage(), # 1
4321+ pdfdoc.nfile, # 2
4322+ npage, # 3
4323+ self.zoom_scale, # 4
4324+ pdfdoc.filename, # 5
4325+ angle, # 6
4326+ crop[0],crop[1], # 7-8
4327+ crop[2],crop[3], # 9-10
4328+ w,h )) # 11-12
4329+ self.update_geometry(iter)
4330+ res = True
4331+
4332+ self.reset_iv_width()
4333+ gobject.idle_add(self.retitle)
4334+ if res:
4335+ gobject.idle_add(self.render)
4336+ return res
4337+
4338+ def choose_export_pdf_name(self, widget=None):
4339+ """Handles choosing a name for exporting """
4340+
4341+ chooser = gtk.FileChooserDialog(title=_('Export ...'),
4342+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
4343+ buttons=(gtk.STOCK_CANCEL,
4344+ gtk.RESPONSE_CANCEL,
4345+ gtk.STOCK_SAVE,
4346+ gtk.RESPONSE_OK))
4347+ chooser.set_do_overwrite_confirmation(True)
4348+ chooser.set_current_folder(self.export_directory)
4349+ filter_pdf = gtk.FileFilter()
4350+ filter_pdf.set_name(_('PDF files'))
4351+ filter_pdf.add_mime_type('application/pdf')
4352+ chooser.add_filter(filter_pdf)
4353+
4354+ filter_all = gtk.FileFilter()
4355+ filter_all.set_name(_('All files'))
4356+ filter_all.add_pattern('*')
4357+ chooser.add_filter(filter_all)
4358+
4359+ while True:
4360+ response = chooser.run()
4361+ if response == gtk.RESPONSE_OK:
4362+ file_out = chooser.get_filename()
4363+ (path, shortname) = os.path.split(file_out)
4364+ (shortname, ext) = os.path.splitext(shortname)
4365+ if ext.lower() != '.pdf':
4366+ file_out = file_out + '.pdf'
4367+ try:
4368+ self.export_to_file(file_out)
4369+ self.export_directory = path
4370+ self.set_unsaved(False)
4371+ except Exception, e:
4372+ chooser.destroy()
4373+ error_msg_dlg = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
4374+ type=gtk.MESSAGE_ERROR,
4375+ message_format=str(e),
4376+ buttons=gtk.BUTTONS_OK)
4377+ response = error_msg_dlg.run()
4378+ if response == gtk.RESPONSE_OK:
4379+ error_msg_dlg.destroy()
4380+ return
4381+ break
4382+ chooser.destroy()
4383+
4384+ def export_to_file(self, file_out):
4385+ """Export to file"""
4386+
4387+ pdf_output = PdfFileWriter()
4388+ pdf_input = []
4389+ for pdfdoc in self.pdfqueue:
4390+ pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb'))
4391+ if pdfdoc_inp.getIsEncrypted():
4392+ try: # Workaround for lp:#355479
4393+ stat = pdfdoc_inp.decrypt('')
4394+ except:
4395+ stat = 0
4396+ if (stat!=1):
4397+ errmsg = _('File %s is encrypted.\n'
4398+ 'Support for encrypted files has not been implemented yet.\n'
4399+ 'File export failed.') % pdfdoc.filename
4400+ raise Exception, errmsg
4401+ #FIXME
4402+ #else
4403+ # ask for password and decrypt file
4404+ pdf_input.append(pdfdoc_inp)
4405+
4406+ for row in self.model:
4407+ # add pages from input to output document
4408+ nfile = row[2]
4409+ npage = row[3]
4410+ current_page = copy(pdf_input[nfile-1].getPage(npage-1))
4411+ angle = row[6]
4412+ angle0 = current_page.get("/Rotate",0)
4413+ crop = [row[7],row[8],row[9],row[10]]
4414+ if angle != 0:
4415+ current_page.rotateClockwise(angle)
4416+ if crop != [0.,0.,0.,0.]:
4417+ rotate_times = (((angle + angle0) % 360 + 45) / 90) % 4
4418+ crop_init = crop
4419+ if rotate_times != 0:
4420+ perm = [0,2,1,3]
4421+ for it in range(rotate_times):
4422+ perm.append(perm.pop(0))
4423+ perm.insert(1,perm.pop(2))
4424+ crop = [crop_init[perm[side]] for side in range(4)]
4425+ #(x1, y1) = current_page.cropBox.lowerLeft
4426+ #(x2, y2) = current_page.cropBox.upperRight
4427+ (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft]
4428+ (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight]
4429+ x1_new = int(x1 + (x2-x1) * crop[0])
4430+ x2_new = int(x2 - (x2-x1) * crop[1])
4431+ y1_new = int(y1 + (y2-y1) * crop[3])
4432+ y2_new = int(y2 - (y2-y1) * crop[2])
4433+ #current_page.cropBox.lowerLeft = (x1_new, y1_new)
4434+ #current_page.cropBox.upperRight = (x2_new, y2_new)
4435+ current_page.mediaBox.lowerLeft = (x1_new, y1_new)
4436+ current_page.mediaBox.upperRight = (x2_new, y2_new)
4437+
4438+ pdf_output.addPage(current_page)
4439+
4440+ # finally, write "output" to document-output.pdf
4441+ print(_('exporting to:'), file_out)
4442+ pdf_output.write(file(file_out, 'wb'))
4443+
4444+ def on_action_add_doc_activate(self, widget, data=None):
4445+ """Import doc"""
4446+
4447+ chooser = gtk.FileChooserDialog(title=_('_Import...'),
4448+ action=gtk.FILE_CHOOSER_ACTION_OPEN,
4449+ buttons=(gtk.STOCK_CANCEL,
4450+ gtk.RESPONSE_CANCEL,
4451+ gtk.STOCK_OPEN,
4452+ gtk.RESPONSE_OK))
4453+ chooser.set_current_folder(self.import_directory)
4454+ chooser.set_select_multiple(True)
4455+
4456+ filter_all = gtk.FileFilter()
4457+ filter_all.set_name(_('All files'))
4458+ filter_all.add_pattern('*')
4459+ chooser.add_filter(filter_all)
4460+
4461+ filter_pdf = gtk.FileFilter()
4462+ filter_pdf.set_name(_('PDF files'))
4463+ filter_pdf.add_mime_type('application/pdf')
4464+ chooser.add_filter(filter_pdf)
4465+ chooser.set_filter(filter_pdf)
4466+
4467+ response = chooser.run()
4468+ if response == gtk.RESPONSE_OK:
4469+ for filename in chooser.get_filenames():
4470+ if os.path.isfile(filename):
4471+ # FIXME
4472+ f = gio.File(filename)
4473+ f_info = f.query_info('standard::content-type')
4474+ mime_type = f_info.get_content_type()
4475+ expected_mime_type = 'application/pdf'
4476+
4477+ if mime_type == expected_mime_type:
4478+ self.add_pdf_pages(filename)
4479+ elif mime_type[:34] == 'application/vnd.oasis.opendocument':
4480+ print(_('OpenDocument not supported yet!'))
4481+ elif mime_type[:5] == 'image':
4482+ print(_('Image file not supported yet!'))
4483+ else:
4484+ print(_('File type not supported!'))
4485+ else:
4486+ print(_('File %s does not exist') % filename)
4487+ elif response == gtk.RESPONSE_CANCEL:
4488+ print(_('Closed, no files selected'))
4489+ chooser.destroy()
4490+ gobject.idle_add(self.retitle)
4491+
4492+ def clear_selected(self, button=None):
4493+ """Removes the selected elements in the IconView"""
4494+
4495+ model = self.iconview.get_model()
4496+ selection = self.iconview.get_selected_items()
4497+ if selection:
4498+ selection.sort(reverse=True)
4499+ self.set_unsaved(True)
4500+ for path in selection:
4501+ iter = model.get_iter(path)
4502+ model.remove(iter)
4503+ path = selection[-1]
4504+ self.iconview.select_path(path)
4505+ if not self.iconview.path_is_selected(path):
4506+ if len(model) > 0: # select the last row
4507+ row = model[-1]
4508+ path = row.path
4509+ self.iconview.select_path(path)
4510+ self.iconview.grab_focus()
4511+
4512+ def iv_drag_begin(self, iconview, context):
4513+ """Sets custom icon on drag begin for multiple items selected"""
4514+
4515+ if len(iconview.get_selected_items()) > 1:
4516+ iconview.stop_emission('drag_begin')
4517+ context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0)
4518+
4519+ def iv_dnd_get_data(self, iconview, context,
4520+ selection_data, target_id, etime):
4521+ """Handles requests for data by drag and drop in iconview"""
4522+
4523+ model = iconview.get_model()
4524+ selection = self.iconview.get_selected_items()
4525+ selection.sort(key=lambda x: x[0])
4526+ data = []
4527+ for path in selection:
4528+ if selection_data.target == 'MODEL_ROW_INTERN':
4529+ data.append(str(path[0]))
4530+ elif selection_data.target == 'MODEL_ROW_EXTERN':
4531+ iter = model.get_iter(path)
4532+ nfile, npage, angle = model.get(iter, 2, 3, 6)
4533+ crop = model.get(iter, 7, 8, 9, 10)
4534+ pdfdoc = self.pdfqueue[nfile - 1]
4535+ data.append('\n'.join([pdfdoc.filename,
4536+ str(npage),
4537+ str(angle)] +
4538+ [str(side) for side in crop]))
4539+ if data:
4540+ data = '\n;\n'.join(data)
4541+ selection_data.set(selection_data.target, 8, data)
4542+
4543+ def iv_dnd_received_data(self, iconview, context, x, y,
4544+ selection_data, target_id, etime):
4545+ """Handles received data by drag and drop in iconview"""
4546+
4547+ model = iconview.get_model()
4548+ data = selection_data.data
4549+ if data:
4550+ data = data.split('\n;\n')
4551+ drop_info = iconview.get_dest_item_at_pos(x, y)
4552+ iter_to = None
4553+ if drop_info:
4554+ path, position = drop_info
4555+ ref_to = gtk.TreeRowReference(model,path)
4556+ else:
4557+ position = gtk.ICON_VIEW_DROP_RIGHT
4558+ if len(model) > 0: #find the iterator of the last row
4559+ row = model[-1]
4560+ path = row.path
4561+ ref_to = gtk.TreeRowReference(model,path)
4562+ if ref_to:
4563+ before = (position == gtk.ICON_VIEW_DROP_LEFT
4564+ or position == gtk.ICON_VIEW_DROP_ABOVE)
4565+ #if target_id == self.MODEL_ROW_INTERN:
4566+ if selection_data.target == 'MODEL_ROW_INTERN':
4567+ if before:
4568+ data.sort(key=int)
4569+ else:
4570+ data.sort(key=int,reverse=True)
4571+ ref_from_list = [gtk.TreeRowReference(model,path)
4572+ for path in data]
4573+ for ref_from in ref_from_list:
4574+ path = ref_to.get_path()
4575+ iter_to = model.get_iter(path)
4576+ path = ref_from.get_path()
4577+ iter_from = model.get_iter(path)
4578+ row = model[iter_from]
4579+ if before:
4580+ model.insert_before(iter_to, row)
4581+ else:
4582+ model.insert_after(iter_to, row)
4583+ if context.action == gtk.gdk.ACTION_MOVE:
4584+ for ref_from in ref_from_list:
4585+ path = ref_from.get_path()
4586+ iter_from = model.get_iter(path)
4587+ model.remove(iter_from)
4588+
4589+ #elif target_id == self.MODEL_ROW_EXTERN:
4590+ elif selection_data.target == 'MODEL_ROW_EXTERN':
4591+ if not before:
4592+ data.reverse()
4593+ while data:
4594+ tmp = data.pop(0).split('\n')
4595+ filename = tmp[0]
4596+ npage, angle = [int(k) for k in tmp[1:3]]
4597+ crop = [float(side) for side in tmp[3:7]]
4598+ if self.add_pdf_pages(filename, npage, npage,
4599+ angle, crop):
4600+ if len(model) > 0:
4601+ path = ref_to.get_path()
4602+ iter_to = model.get_iter(path)
4603+ row = model[-1] #the last row
4604+ path = row.path
4605+ iter_from = model.get_iter(path)
4606+ if before:
4607+ model.move_before(iter_from, iter_to)
4608+ else:
4609+ model.move_after(iter_from, iter_to)
4610+ if context.action == gtk.gdk.ACTION_MOVE:
4611+ context.finish(True, True, etime)
4612+
4613+ def iv_dnd_data_delete(self, widget, context):
4614+ """Deletes dnd items after a successful move operation"""
4615+
4616+ model = self.iconview.get_model()
4617+ selection = self.iconview.get_selected_items()
4618+ ref_del_list = [gtk.TreeRowReference(model,path) for path in selection]
4619+ for ref_del in ref_del_list:
4620+ path = ref_del.get_path()
4621+ iter = model.get_iter(path)
4622+ model.remove(iter)
4623+
4624+ def iv_dnd_motion(self, iconview, context, x, y, etime):
4625+ """Handles the drag-motion signal in order to auto-scroll the view"""
4626+
4627+ autoscroll_area = 40
4628+ sw_vadj = self.sw.get_vadjustment()
4629+ sw_height = self.sw.get_allocation().height
4630+ if y -sw_vadj.get_value() < autoscroll_area:
4631+ if not self.iv_auto_scroll_timer:
4632+ self.iv_auto_scroll_direction = gtk.DIR_UP
4633+ self.iv_auto_scroll_timer = gobject.timeout_add(150,
4634+ self.iv_auto_scroll)
4635+ elif y -sw_vadj.get_value() > sw_height - autoscroll_area:
4636+ if not self.iv_auto_scroll_timer:
4637+ self.iv_auto_scroll_direction = gtk.DIR_DOWN
4638+ self.iv_auto_scroll_timer = gobject.timeout_add(150,
4639+ self.iv_auto_scroll)
4640+ elif self.iv_auto_scroll_timer:
4641+ gobject.source_remove(self.iv_auto_scroll_timer)
4642+ self.iv_auto_scroll_timer = None
4643+
4644+ def iv_dnd_leave_end(self, widget, context, ignored=None):
4645+ """Ends the auto-scroll during DND"""
4646+
4647+ if self.iv_auto_scroll_timer:
4648+ gobject.source_remove(self.iv_auto_scroll_timer)
4649+ self.iv_auto_scroll_timer = None
4650+
4651+ def iv_auto_scroll(self):
4652+ """Timeout routine for auto-scroll"""
4653+
4654+ sw_vadj = self.sw.get_vadjustment()
4655+ sw_vpos = sw_vadj.get_value()
4656+ if self.iv_auto_scroll_direction == gtk.DIR_UP:
4657+ sw_vpos -= sw_vadj.step_increment
4658+ sw_vadj.set_value(max(sw_vpos, sw_vadj.lower))
4659+ elif self.iv_auto_scroll_direction == gtk.DIR_DOWN:
4660+ sw_vpos += sw_vadj.step_increment
4661+ sw_vadj.set_value(min(sw_vpos, sw_vadj.upper - sw_vadj.page_size))
4662+ return True #call me again
4663+
4664+ def iv_button_press_event(self, iconview, event):
4665+ """Manages mouse clicks on the iconview"""
4666+
4667+ if event.button == 3:
4668+ x = int(event.x)
4669+ y = int(event.y)
4670+ time = event.time
4671+ path = iconview.get_path_at_pos(x, y)
4672+ selection = iconview.get_selected_items()
4673+ if path:
4674+ if path not in selection:
4675+ iconview.unselect_all()
4676+ iconview.select_path(path)
4677+ iconview.grab_focus()
4678+ self.popup.popup(None, None, None, event.button, time)
4679+ return 1
4680+
4681+ def sw_dnd_received_data(self, scrolledwindow, context, x, y,
4682+ selection_data, target_id, etime):
4683+ """Handles received data by drag and drop in scrolledwindow"""
4684+
4685+ data = selection_data.data
4686+ if target_id == self.MODEL_ROW_EXTERN:
4687+ self.model
4688+ if data:
4689+ data = data.split('\n;\n')
4690+ while data:
4691+ tmp = data.pop(0).split('\n')
4692+ filename = tmp[0]
4693+ npage, angle = [int(k) for k in tmp[1:3]]
4694+ crop = [float(side) for side in tmp[3:7]]
4695+ if self.add_pdf_pages(filename, npage, npage, angle, crop):
4696+ if context.action == gtk.gdk.ACTION_MOVE:
4697+ context.finish(True, True, etime)
4698+ elif target_id == self.TEXT_URI_LIST:
4699+ uri = data.strip()
4700+ uri_splitted = uri.split() # we may have more than one file dropped
4701+ for uri in uri_splitted:
4702+ filename = self.get_file_path_from_dnd_dropped_uri(uri)
4703+ if os.path.isfile(filename): # is it file?
4704+ self.add_pdf_pages(filename)
4705+
4706+ def sw_button_press_event(self, scrolledwindow, event):
4707+ """Unselects all items in iconview on mouse click in scrolledwindow"""
4708+
4709+ if event.button == 1:
4710+ self.iconview.unselect_all()
4711+
4712+ def sw_scroll_event(self, scrolledwindow, event):
4713+ """Manages mouse scroll events in scrolledwindow"""
4714+
4715+ if event.state & gtk.gdk.CONTROL_MASK:
4716+ if event.direction == gtk.gdk.SCROLL_UP:
4717+ self.zoom_change(1)
4718+ return 1
4719+ elif event.direction == gtk.gdk.SCROLL_DOWN:
4720+ self.zoom_change(-1)
4721+ return 1
4722+
4723+ def zoom_set(self, level):
4724+ """Sets the zoom level"""
4725+ self.zoom_level = max(min(level, 5), -24)
4726+ self.zoom_scale = 1.1 ** self.zoom_level
4727+ for row in self.model:
4728+ row[4] = self.zoom_scale
4729+ self.reset_iv_width()
4730+
4731+ def zoom_change(self, step=5):
4732+ """Modifies the zoom level"""
4733+ self.zoom_set(self.zoom_level + step)
4734+
4735+ def zoom_in(self, widget=None):
4736+ """Increases the zoom level by 5 steps"""
4737+ self.zoom_change(5)
4738+
4739+ def zoom_out(self, widget=None, step=5):
4740+ """Reduces the zoom level by 5 steps"""
4741+ self.zoom_change(-5)
4742+
4743+ def get_file_path_from_dnd_dropped_uri(self, uri):
4744+ """Extracts the path from an uri"""
4745+
4746+ path = urllib.url2pathname(uri) # escape special chars
4747+ path = path.strip('\r\n\x00') # remove \r\n and NULL
4748+
4749+ # get the path to file
4750+ if path.startswith('file:\\\\\\'): # windows
4751+ path = path[8:] # 8 is len('file:///')
4752+ elif path.startswith('file://'): # nautilus, rox
4753+ path = path[7:] # 7 is len('file://')
4754+ elif path.startswith('file:'): # xffm
4755+ path = path[5:] # 5 is len('file:')
4756+ return path
4757+
4758+ def rotate_page_right(self, widget, data=None):
4759+ self.rotate_page(90)
4760+
4761+ def rotate_page_left(self, widget, data=None):
4762+ self.rotate_page(-90)
4763+
4764+ def rotate_page(self, angle):
4765+ """Rotates the selected page in the IconView"""
4766+
4767+ model = self.iconview.get_model()
4768+ selection = self.iconview.get_selected_items()
4769+ if len(selection) > 0:
4770+ self.set_unsaved(True)
4771+ rotate_times = (((-angle) % 360 + 45) / 90) % 4
4772+ if rotate_times is not 0:
4773+ for path in selection:
4774+ iter = model.get_iter(path)
4775+ nfile = model.get_value(iter, 2)
4776+ npage = model.get_value(iter, 3)
4777+
4778+ crop = [0.,0.,0.,0.]
4779+ perm = [0,2,1,3]
4780+ for it in range(rotate_times):
4781+ perm.append(perm.pop(0))
4782+ perm.insert(1,perm.pop(2))
4783+ crop = [model.get_value(iter, 7 + perm[side]) for side in range(4)]
4784+ for side in range(4):
4785+ model.set_value(iter, 7 + side, crop[side])
4786+
4787+ new_angle = model.get_value(iter, 6) + int(angle)
4788+ new_angle = new_angle % 360
4789+ model.set_value(iter, 6, new_angle)
4790+ self.update_geometry(iter)
4791+ self.reset_iv_width()
4792+
4793+ def crop_page_dialog(self, widget):
4794+ """Opens a dialog box to define margins for page cropping"""
4795+
4796+ sides = ('L', 'R', 'T', 'B')
4797+ side_names = {'L':_('Left'), 'R':_('Right'),
4798+ 'T':_('Top'), 'B':_('Bottom') }
4799+ opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' }
4800+
4801+ def set_crop_value(spinbutton, side):
4802+ opp_side = opposite_sides[side]
4803+ pos = sides.index(opp_side)
4804+ adj = spin_list[pos].get_adjustment()
4805+ adj.set_upper(99.0 - spinbutton.get_value())
4806+
4807+ model = self.iconview.get_model()
4808+ selection = self.iconview.get_selected_items()
4809+
4810+ crop = [0.,0.,0.,0.]
4811+ if selection:
4812+ path = selection[0]
4813+ pos = model.get_iter(path)
4814+ crop = [model.get_value(pos, 7 + side) for side in range(4)]
4815+
4816+ dialog = gtk.Dialog(title=(_('Crop Selected Pages')),
4817+ parent=self.window,
4818+ flags=gtk.DIALOG_MODAL,
4819+ buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
4820+ gtk.STOCK_OK, gtk.RESPONSE_OK))
4821+ dialog.set_size_request(340, 250)
4822+ dialog.set_default_response(gtk.RESPONSE_OK)
4823+
4824+ frame = gtk.Frame(_('Crop Margins'))
4825+ dialog.vbox.pack_start(frame, False, False, 20)
4826+
4827+ vbox = gtk.VBox(False, 0)
4828+ frame.add(vbox)
4829+
4830+ spin_list = []
4831+ units = 2 * [_('% of width')] + 2 * [_('% of height')]
4832+ for side in sides:
4833+ hbox = gtk.HBox(True, 0)
4834+ vbox.pack_start(hbox, False, False, 5)
4835+
4836+ label = gtk.Label(side_names[side])
4837+ label.set_alignment(0, 0.0)
4838+ hbox.pack_start(label, True, True, 20)
4839+
4840+ adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0)
4841+ spin = gtk.SpinButton(adj, 0, 1)
4842+ spin.set_activates_default(True)
4843+ spin.connect('value-changed', set_crop_value, side)
4844+ spin_list.append(spin)
4845+ hbox.pack_start(spin, False, False, 30)
4846+
4847+ label = gtk.Label(units.pop(0))
4848+ label.set_alignment(0, 0.0)
4849+ hbox.pack_start(label, True, True, 0)
4850+
4851+ dialog.show_all()
4852+ result = dialog.run()
4853+
4854+ if result == gtk.RESPONSE_OK:
4855+ modified = False
4856+ crop = [spin.get_value()/100. for spin in spin_list]
4857+ for path in selection:
4858+ pos = model.get_iter(path)
4859+ for it in range(4):
4860+ old_val = model.get_value(pos, 7 + it)
4861+ model.set_value(pos, 7 + it, crop[it])
4862+ if crop[it] != old_val:
4863+ modified = True
4864+ self.update_geometry(pos)
4865+ if modified:
4866+ self.set_unsaved(True)
4867+ self.reset_iv_width()
4868+ elif result == gtk.RESPONSE_CANCEL:
4869+ print(_('Dialog closed'))
4870+ dialog.destroy()
4871+
4872+ def about_dialog(self, widget, data=None):
4873+ about_dialog = gtk.AboutDialog()
4874+ try:
4875+ about_dialog.set_transient_for(self.window)
4876+ about_dialog.set_modal(True)
4877+ except:
4878+ pass
4879+ # FIXME
4880+ about_dialog.set_name(APPNAME)
4881+ about_dialog.set_version(VERSION)
4882+ about_dialog.set_comments(_(
4883+ '%s is a tool for rearranging and modifying PDF files.' \
4884+ 'Developed using GTK+ and Python') % APPNAME)
4885+ about_dialog.set_authors(['Konstantinos Poulios',])
4886+ about_dialog.set_website_label(WEBSITE)
4887+ about_dialog.set_logo_icon_name('pdfshuffler')
4888+ about_dialog.set_license(LICENSE)
4889+ about_dialog.connect('response', lambda w, a: about_dialog.destroy())
4890+ about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy())
4891+ about_dialog.show_all()
4892+
4893+
4894+class PDF_Doc:
4895+ """Class handling PDF documents"""
4896+
4897+ def __init__(self, filename, nfile, tmp_dir):
4898+
4899+ self.filename = os.path.abspath(filename)
4900+ (self.path, self.shortname) = os.path.split(self.filename)
4901+ (self.shortname, self.ext) = os.path.splitext(self.shortname)
4902+ f = gio.File(filename)
4903+ mime_type = f.query_info('standard::content-type').get_content_type()
4904+ expected_mime_type = 'application/pdf'
4905+ file_prefix = 'file://'
4906+
4907+ if mime_type == expected_mime_type:
4908+ self.nfile = nfile + 1
4909+ self.mtime = os.path.getmtime(filename)
4910+ self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile +
4911+ self.shortname + '.pdf')
4912+ shutil.copy(self.filename, self.copyname)
4913+ self.document = poppler.document_new_from_file (file_prefix + self.copyname, None)
4914+ self.npage = self.document.get_n_pages()
4915+ else:
4916+ self.nfile = 0
4917+ self.npage = 0
4918+
4919+
4920+class PDF_Renderer(threading.Thread,gobject.GObject):
4921+
4922+ def __init__(self, model, pdfqueue):
4923+ threading.Thread.__init__(self)
4924+ gobject.GObject.__init__(self)
4925+ self.model = model
4926+ self.pdfqueue = pdfqueue
4927+ self.quit = False
4928+
4929+ def run(self):
4930+ for idx, row in enumerate(self.model):
4931+ if self.quit:
4932+ return
4933+ if not row[1].surface:
4934+ try:
4935+ nfile = row[2]
4936+ npage = row[3]
4937+ pdfdoc = self.pdfqueue[nfile - 1]
4938+ page = pdfdoc.document.get_page(npage-1)
4939+ w, h = page.get_size()
4940+ thumbnail = CairoImage(int(w), int(h))
4941+ cr = cairo.Context(thumbnail.surface)
4942+ page.render(cr)
4943+ time.sleep(0.003)
4944+ gobject.idle_add(self.emit,'update_thumbnail', idx, thumbnail,
4945+ priority=gobject.PRIORITY_LOW)
4946+ except Exception,e:
4947+ print e
4948+
4949+
4950+def main():
4951+ """This function starts PdfShuffler"""
4952+ gtk.gdk.threads_init()
4953+ gobject.threads_init()
4954+ PdfShuffler()
4955+ gtk.main()
4956+
4957+if __name__ == '__main__':
4958+ main()
4959+
4960
4961=== modified file 'po/genpot.sh'
4962--- po/genpot.sh 2009-05-16 23:26:32 +0000
4963+++ po/genpot.sh 2012-02-12 00:35:23 +0000
4964@@ -21,4 +21,5 @@
4965 #
4966
4967 # Make translation files
4968-xgettext -L python -o po/pdfshuffler.pot pdfshuffler
4969+intltool-extract --type=gettext/glade data/pdfshuffler.ui
4970+xgettext --language=Python --keyword=_ --keyword=N_ --output=po/pdfshuffler.pot pdfshuffler data/pdfshuffler.ui.h
4971
4972=== added file 'po/ja.po'
4973--- po/ja.po 1970-01-01 00:00:00 +0000
4974+++ po/ja.po 2012-02-12 00:35:23 +0000
4975@@ -0,0 +1,156 @@
4976+# SOME DESCRIPTIVE TITLE.
4977+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
4978+# This file is distributed under the same license as the PACKAGE package.
4979+# Toshiharu Kudoh <toshi.kd2@gmail.com>, 2010.
4980+#
4981+
4982+msgid ""
4983+msgstr ""
4984+"Project-Id-Version: 0.5.1\n"
4985+"Report-Msgid-Bugs-To: \n"
4986+"POT-Creation-Date: 2010-02-11 12:51+JST\n"
4987+"PO-Revision-Date: 2010-11-20 01:48+JST\n"
4988+"Last-Translator: Toshiharu Kudoh <toshi.kd2@gmail.com>\n"
4989+"Language-Team: Japanese <LL@li.org>\n"
4990+"MIME-Version: 1.0\n"
4991+"Content-Type: text/plain; charset=UTF-8\n"
4992+"Content-Transfer-Encoding: 8bit\n"
4993+
4994+#: pdfshuffler:101 pdfshuffler:520
4995+#, python-format
4996+msgid "File %s does not exist"
4997+msgstr "%s は存在しません"
4998+
4999+#: pdfshuffler:205 pdfshuffler:243
5000+msgid "Delete Page(s)"
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches