wuffl 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README +34 -0
- data/bin/wuffl +6 -0
- data/lib/wuffl/actions.rb +290 -0
- data/lib/wuffl/basic_elements.rb +109 -0
- data/lib/wuffl/image_actions.rb +90 -0
- data/lib/wuffl/wuffl_interface.rb +88 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 46a0f50d3457bec156544cc167f04c2a712dfc8a4b4ea8cf80a17f8b5247d7f0
|
4
|
+
data.tar.gz: b5cdc53358c6cc5ab9e2b639c8ef4a4de365a680b3d5805cbd420ce6937f5e03
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 64c69f5ee91b1282466a323d358f669407dbc7d27a29df5c3801922ff2fe93bde350ebed881c760d87672eb660c1a0cc5e05c58aacedd36c4b676fd9df45ed71
|
7
|
+
data.tar.gz: 97a5dbcedb88dba60a458d8bcfc2a5dd4d5e8a2b328101257018c578eb870a9e5435bef8e207bac52dd8e13ad6ef1b2e26f76a9381c315209eda7d26affcdd77
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Anna Foerster
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Wuffl
|
2
|
+
<strong> 1. Introduction </strong>
|
3
|
+
Wuffl is a simple image viewer which can move pictures either to a „Selected“ folder or to a „Deleted“ folder. "Selected" and "Deleted" folders are located in the same folder as the chosen picture. Wuffl is available for Linux Ubuntu >=14.04, Mac and Windows.
|
4
|
+
|
5
|
+
Imagine you review you vacation photographs and you need to select which pictures to show to your friends at the next party or you work your way through a bunch of old pictures and you need to select a view of them for a anniversary celebration. Wuffl allows you to move the picture to an extra folder ("Selected" folder) or to a "Deleted" folder while viewing it.
|
6
|
+
<p>
|
7
|
+
Source for the icons of the buttons: http://icons.mysitemyway.com
|
8
|
+
</p>
|
9
|
+
<strong> 2. Installation </strong>
|
10
|
+
To use the Wuffl image viewer you require:
|
11
|
+
<ul>
|
12
|
+
<li> <a href="https://www.ruby-lang.org/en/downloads/"><code>ruby</code></a> (v2.4)
|
13
|
+
</ul>
|
14
|
+
as well as the following Ruby gems:
|
15
|
+
<ul>
|
16
|
+
<li><code>fastimage</code>
|
17
|
+
<li><code>fileutils</code>
|
18
|
+
<li><code>gtk3</code>
|
19
|
+
<li><code>stringio</code></li>
|
20
|
+
</ul>
|
21
|
+
|
22
|
+
Get the Wullf image viewer by typing ```gem install wuffl``` in your command line.
|
23
|
+
|
24
|
+
<strong>3. Usage</strong>
|
25
|
+
Using your terminal or cmd start Wuffl by typing
|
26
|
+
<code> wuffl</code>
|
27
|
+
|
28
|
+
After starting Wuffl you'll see a start screen with a line of buttons at the bottom.
|
29
|
+
To select an image go to the top left corner, klick on <code>File</code> → <code>Open file</code> and browse to the location of you images. Then select an image and click <code>open</code> or just doubleclick on the selected image.
|
30
|
+
|
31
|
+
The buttons from left to right mean: <code>show previous image</code>, <code>rotate current image</code>, <code>move the current image to the folder 'Selected'</code>, <code>show next image</code>, <code>move the current image to the folder 'Deleted'</code>.
|
32
|
+
|
33
|
+
<strong>4. Remarks</strong>
|
34
|
+
If you have any remarks, bugs, questions etc. please tell me, I'd be happy to help.
|
data/bin/wuffl
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
require_relative 'image_actions'
|
2
|
+
|
3
|
+
module Wuffl
|
4
|
+
module Actions
|
5
|
+
|
6
|
+
# get the operating system of the machine
|
7
|
+
def self.get_op_sys
|
8
|
+
gempath = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
9
|
+
gempath.sub!("/lib", "")
|
10
|
+
if (/darwin/ =~ RUBY_PLATFORM) != nil
|
11
|
+
return 'mac'
|
12
|
+
elsif (/linux/ =~ RUBY_PLATFORM) != nil
|
13
|
+
return 'linux'
|
14
|
+
elsif (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
|
15
|
+
return 'windows'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# get the resolution of the display
|
20
|
+
def self.get_resolution
|
21
|
+
|
22
|
+
operating_system = get_op_sys
|
23
|
+
|
24
|
+
case operating_system
|
25
|
+
|
26
|
+
when 'windows'
|
27
|
+
output_dimensions = `wmic path Win32_VideoController get VideoModeDescription,CurrentVerticalResolution,CurrentHorizontalResolution /format:value`
|
28
|
+
begin_hor_res = output_dimensions.index("HorizontalResolution")
|
29
|
+
begin_ver_res = output_dimensions.index("VerticalResolution")
|
30
|
+
begin_video_mode = output_dimensions.index("VideoMode")
|
31
|
+
length_hor = "HorizontalResolution=".length
|
32
|
+
length_ver = "VerticalResolution=".length
|
33
|
+
length_video = "VideoMode=".length
|
34
|
+
char_hor = output_dimensions[begin_hor_res + length_hor]
|
35
|
+
char_ver = output_dimensions[begin_ver_res + length_ver]
|
36
|
+
char_video = output_dimensions[begin_video_mode + length_video]
|
37
|
+
if char_hor != char_hor.to_i.to_s
|
38
|
+
output_dimensions.sub!("HorizontalResolution", "")
|
39
|
+
end
|
40
|
+
if char_ver != char_ver.to_i.to_s
|
41
|
+
output_dimensions.sub!("VerticalResolution", "")
|
42
|
+
end
|
43
|
+
if char_video != char_video.to_i.to_s
|
44
|
+
output_dimensions.sub!("VideoMode", "")
|
45
|
+
end
|
46
|
+
|
47
|
+
# Horizontal Resolution of the screen
|
48
|
+
hor_res_begin_index = output_dimensions.index("HorizontalResolution") + "HorizontalResolution=".length
|
49
|
+
hor_res_end_index = output_dimensions.index("CurrentVertical") - 3
|
50
|
+
win_width = output_dimensions.slice(hor_res_begin_index..hor_res_end_index).to_i
|
51
|
+
|
52
|
+
# Vertical Resolution of the screen
|
53
|
+
ver_res_begin_index = output_dimensions.index("VerticalResolution") + "VerticalResolution=".length
|
54
|
+
ver_res_end_index = output_dimensions.index("VideoMode") - 3
|
55
|
+
win_height = output_dimensions.slice(ver_res_begin_index..ver_res_end_index).to_i
|
56
|
+
|
57
|
+
else
|
58
|
+
if operating_system == "mac"
|
59
|
+
output_dimensions = `system_profiler SPDisplaysDataType |grep Resolution`.chomp.tr(" ", "")
|
60
|
+
end_index = output_dimensions.index("Retina") - 1
|
61
|
+
elsif operating_system == "linux"
|
62
|
+
output_dimensions = `xdpyinfo | grep dimensions`.chomp.tr(" ", "")
|
63
|
+
end_index = output_dimensions.index("p") - 1
|
64
|
+
end
|
65
|
+
start_index = output_dimensions.index(":") + 1
|
66
|
+
resolution = output_dimensions.slice(start_index..end_index)
|
67
|
+
win_width = resolution.slice(0..resolution.index("x")-1).to_i
|
68
|
+
win_height = resolution.slice(resolution.index("x")+1.. resolution.length-1).to_i
|
69
|
+
end
|
70
|
+
|
71
|
+
return [win_width, win_height]
|
72
|
+
end
|
73
|
+
|
74
|
+
# pack the boxes for the GUI
|
75
|
+
def self.pack_boxes(window, img_current, box_set, button_set)
|
76
|
+
box_set[:hbox].add button_set[:prev_btn]
|
77
|
+
box_set[:hbox].add button_set[:select_btn]
|
78
|
+
box_set[:hbox].add button_set[:rotate_btn]
|
79
|
+
box_set[:hbox].add button_set[:delete_btn]
|
80
|
+
box_set[:hbox].add button_set[:next_btn]
|
81
|
+
|
82
|
+
box_set[:halign].add box_set[:hbox]
|
83
|
+
|
84
|
+
box_set[:vbox].pack_start box_set[:mb], :expand => false, :fill => false, :padding => 5
|
85
|
+
box_set[:vbox].pack_start img_current, :expand => true, :fill => true, :padding => 5
|
86
|
+
box_set[:vbox].pack_start box_set[:halign], :expand => false, :fill => false, :padding => 5
|
87
|
+
|
88
|
+
|
89
|
+
window.add box_set[:vbox]
|
90
|
+
|
91
|
+
return box_set
|
92
|
+
end
|
93
|
+
|
94
|
+
# set the index for the next image
|
95
|
+
def self.set_next_index(index, img_array)
|
96
|
+
if (index + 1) <= (img_array.length - 1)
|
97
|
+
index += 1
|
98
|
+
else
|
99
|
+
index = 0
|
100
|
+
end
|
101
|
+
return index
|
102
|
+
end
|
103
|
+
|
104
|
+
# set the index for the previous image
|
105
|
+
def self.set_prev_index(index, img_array)
|
106
|
+
if (index - 1) < 0
|
107
|
+
index = img_array.length - 1
|
108
|
+
else
|
109
|
+
index -= 1
|
110
|
+
end
|
111
|
+
return index
|
112
|
+
end
|
113
|
+
|
114
|
+
# deactivate all buttons
|
115
|
+
def self.deactivate_buttons (button_set)
|
116
|
+
button_set.each do |button, sens_value|
|
117
|
+
button_set[button].sensitive = false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# action when the "Open file" menu option is clicked
|
122
|
+
def self.open_file_action(window, img_parameters, button_set)
|
123
|
+
dialog = Gtk::FileChooserDialog.new(:title => "Open file", :parent => window, :action => Gtk::FileChooserAction::OPEN,
|
124
|
+
:buttons => [[Gtk::Stock::OPEN, Gtk::ResponseType::ACCEPT], [Gtk::Stock::CANCEL, Gtk::ResponseType::CANCEL]])
|
125
|
+
|
126
|
+
if dialog.run == Gtk::ResponseType::ACCEPT
|
127
|
+
filename = dialog.filename
|
128
|
+
img_parameters[:dir_path] = File.dirname(filename)
|
129
|
+
img_parameters[:selected_path] = img_parameters[:dir_path] + "/Selected"
|
130
|
+
img_parameters[:deleted_path] = img_parameters[:dir_path] + "/Deleted"
|
131
|
+
all_files = Dir.entries(img_parameters[:dir_path])
|
132
|
+
accepted_formats = [".jpg", ".JPG", ".png", ".PNG", ".gif", ".GIF"]
|
133
|
+
img_parameters[:is_landscape] = true
|
134
|
+
|
135
|
+
all_files.each do |name|
|
136
|
+
if accepted_formats.include? File.extname(name)
|
137
|
+
img_parameters[:all_orig_img] << name
|
138
|
+
end
|
139
|
+
end
|
140
|
+
img_parameters[:all_orig_img] = img_parameters[:all_orig_img].sort
|
141
|
+
just_the_name = File.basename filename
|
142
|
+
|
143
|
+
img_parameters[:all_orig_img].each_with_index do |name, index|
|
144
|
+
if just_the_name == name
|
145
|
+
img_parameters[:ind] = index
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
if File.directory?(img_parameters[:selected_path]) == false
|
150
|
+
Dir.mkdir(img_parameters[:selected_path])
|
151
|
+
FileUtils.chmod 0777, img_parameters[:selected_path]
|
152
|
+
end
|
153
|
+
|
154
|
+
if File.directory?(img_parameters[:deleted_path]) == false
|
155
|
+
Dir.mkdir(img_parameters[:deleted_path])
|
156
|
+
FileUtils.chmod 0777, img_parameters[:deleted_path]
|
157
|
+
end
|
158
|
+
|
159
|
+
# Prebuffer of all files
|
160
|
+
img_parameters[:all_orig_img].each do |file|
|
161
|
+
current_filename = img_parameters[:dir_path] + "/" + file
|
162
|
+
if current_filename.include?("\\")
|
163
|
+
current_filename.gsub!("\\", "/")
|
164
|
+
end
|
165
|
+
img_parameters[:all_orig_pb], img_parameters[:is_landscape] = ImageActions.prepare_pixbuf(current_filename, img_parameters[:all_orig_pb], img_parameters[:img_max_w], img_parameters[:img_max_h], img_parameters[:reduction_factor])
|
166
|
+
end
|
167
|
+
img_parameters[:pb_current] = img_parameters[:all_orig_pb][img_parameters[:ind]]
|
168
|
+
|
169
|
+
ImageActions.show_img(img_parameters[:img_current], img_parameters[:pb_current])
|
170
|
+
window.set_title File.basename filename
|
171
|
+
|
172
|
+
# Activate all buttons
|
173
|
+
button_set.each do |button, sens_value|
|
174
|
+
button_set[button].sensitive = true
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
dialog.destroy
|
179
|
+
|
180
|
+
return img_parameters
|
181
|
+
end
|
182
|
+
|
183
|
+
# action when the "previous image" button or the "next image" button is clicked
|
184
|
+
def self.prev_next_btn_action(window, img_parameters, previous_or_next_button)
|
185
|
+
if previous_or_next_button == "prev" # previous image
|
186
|
+
img_parameters[:ind] = set_prev_index(img_parameters[:ind], img_parameters[:all_orig_img])
|
187
|
+
elsif previous_or_next_button == "next" # next button
|
188
|
+
img_parameters[:ind] = set_next_index(img_parameters[:ind], img_parameters[:all_orig_img])
|
189
|
+
end
|
190
|
+
filename = img_parameters[:dir_path] + "/" + img_parameters[:all_orig_img][img_parameters[:ind]]
|
191
|
+
img_parameters[:is_landscape] = ImageActions.img_dimensions_fi(filename)[0]
|
192
|
+
img_parameters[:pb_current] = img_parameters[:all_orig_pb][img_parameters[:ind]]
|
193
|
+
ImageActions.show_img(img_parameters[:img_current], img_parameters[:pb_current])
|
194
|
+
window.set_title File.basename filename
|
195
|
+
|
196
|
+
return img_parameters
|
197
|
+
end
|
198
|
+
|
199
|
+
# action when the rotate button is clicked
|
200
|
+
def self.rotate_btn_action(img_parameters)
|
201
|
+
if img_parameters[:is_landscape] == false # Original image in portrait format
|
202
|
+
img_parameters[:pb_current] = img_parameters[:pb_current].rotate(:clockwise)
|
203
|
+
|
204
|
+
else # Original image in landscape format
|
205
|
+
if img_parameters[:rotation_case] == 'A'
|
206
|
+
img_parameters[:pb_current] = img_parameters[:pb_current].rotate(:clockwise)
|
207
|
+
width_pb = img_parameters[:pb_current].width
|
208
|
+
height_pb = img_parameters[:pb_current].height
|
209
|
+
if height_pb > img_parameters[:img_max_h]
|
210
|
+
while height_pb > img_parameters[:img_max_h] do
|
211
|
+
height_pb *= img_parameters[:reduction_factor]
|
212
|
+
width_pb *= img_parameters[:reduction_factor]
|
213
|
+
end
|
214
|
+
height_pb = height_pb.to_i
|
215
|
+
width_pb = width_pb.to_i
|
216
|
+
img_parameters[:pb_portrait] = img_parameters[:pb_current].scale(width_pb, height_pb, :bilinear)
|
217
|
+
img_parameters[:pb_current] = img_parameters[:pb_portrait]
|
218
|
+
else
|
219
|
+
img_parameters[:pb_portrait] = img_parameters[:pb_current]
|
220
|
+
end
|
221
|
+
img_parameters[:rotation_case] = 'B'
|
222
|
+
|
223
|
+
elsif img_parameters[:rotation_case] == 'B'
|
224
|
+
img_parameters[:pb_current] = img_parameters[:all_orig_pb][img_parameters[:ind]].rotate(:upsidedown)
|
225
|
+
img_parameters[:rotation_case] = 'C'
|
226
|
+
|
227
|
+
elsif img_parameters[:rotation_case] == 'C'
|
228
|
+
img_parameters[:pb_current] = img_parameters[:pb_portrait].rotate(:upsidedown)
|
229
|
+
img_parameters[:rotation_case] = 'D'
|
230
|
+
|
231
|
+
elsif img_parameters[:rotation_case] == 'D'
|
232
|
+
img_parameters[:pb_current] = img_parameters[:all_orig_pb][img_parameters[:ind]]
|
233
|
+
img_parameters[:rotation_case] = 'A'
|
234
|
+
end
|
235
|
+
end
|
236
|
+
img_parameters[:img_current].set_pixbuf(img_parameters[:pb_current])
|
237
|
+
|
238
|
+
return img_parameters
|
239
|
+
end
|
240
|
+
|
241
|
+
# action for the select and delete buttons
|
242
|
+
def self.select_delete_btn_action(window, img_parameters, button_set, select_delete_par)
|
243
|
+
if img_parameters[:all_orig_img].length > 0
|
244
|
+
|
245
|
+
just_the_name = img_parameters[:all_orig_img][img_parameters[:ind]]
|
246
|
+
current_location = img_parameters[:dir_path] + "/" + just_the_name
|
247
|
+
if select_delete_par == "select"
|
248
|
+
new_location = img_parameters[:selected_path] + "/" + just_the_name
|
249
|
+
elsif select_delete_par == "delete"
|
250
|
+
new_location = img_parameters[:deleted_path] + "/" + just_the_name
|
251
|
+
end
|
252
|
+
FileUtils.mv(current_location, new_location)
|
253
|
+
|
254
|
+
img_parameters[:all_orig_img].delete_at(img_parameters[:ind])
|
255
|
+
img_parameters[:all_orig_pb].delete_at(img_parameters[:ind])
|
256
|
+
|
257
|
+
if img_parameters[:all_orig_img].length > 0
|
258
|
+
if img_parameters[:ind] == img_parameters[:all_orig_img].length
|
259
|
+
img_parameters[:ind] -= 1
|
260
|
+
end
|
261
|
+
|
262
|
+
just_the_name = img_parameters[:all_orig_img][img_parameters[:ind]]
|
263
|
+
filename = img_parameters[:dir_path] + "/" + just_the_name
|
264
|
+
img_parameters[:is_landscape] = ImageActions.img_dimensions_fi(filename)[0]
|
265
|
+
img_parameters[:pb_current] = img_parameters[:all_orig_pb][img_parameters[:ind]]
|
266
|
+
ImageActions.show_img(img_parameters[:img_current], img_parameters[:pb_current])
|
267
|
+
window.set_title just_the_name
|
268
|
+
|
269
|
+
if select_delete_par == "select"
|
270
|
+
# Buffer the next image if not all loaded yet
|
271
|
+
if img_parameters[:all_orig_pb].length != img_parameters[:all_orig_img].length
|
272
|
+
current_filename = img_parameters[:dir_path] + "/" + img_parameters[:all_orig_img][img_parameters[:ind]+1]
|
273
|
+
img_parameters[:all_orig_pb], img_parameters[:is_landscape] = ImageActions.prepare_pixbuf(current_filename, img_parameters[:all_orig_pb], img_parameters[:img_max_w], img_parameters[:img_max_h], img_parameters[:reduction_factor])
|
274
|
+
end
|
275
|
+
end
|
276
|
+
else
|
277
|
+
img_parameters[:pb_current] = GdkPixbuf::Pixbuf.new
|
278
|
+
img_parameters[:img_current].set_pixbuf(img_parameters[:pb_current])
|
279
|
+
deactivate_buttons(button_set)
|
280
|
+
end
|
281
|
+
else
|
282
|
+
img_parameters[:pb_current] = GdkPixbuf::Pixbuf.new
|
283
|
+
img_parameters[:img_current].set_pixbuf(img_parameters[:pb_current])
|
284
|
+
deactivate_buttons(button_set)
|
285
|
+
end
|
286
|
+
return img_parameters
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Wuffl
|
2
|
+
module BasicElements
|
3
|
+
|
4
|
+
# define the interface window
|
5
|
+
def self.define_window(win_width, win_height)
|
6
|
+
window = Gtk::Window.new
|
7
|
+
window.set_title "Wuffl"
|
8
|
+
window.set_window_position :center
|
9
|
+
window.set_default_size win_width, win_height
|
10
|
+
window.signal_connect("destroy") {Gtk.main_quit}
|
11
|
+
|
12
|
+
return window
|
13
|
+
end
|
14
|
+
|
15
|
+
# define the boxes which are parts of the window
|
16
|
+
def self.define_boxes
|
17
|
+
vbox = Gtk::Box.new :vertical, 5
|
18
|
+
hbox = Gtk::Box.new :horizontal, 10
|
19
|
+
|
20
|
+
return [vbox, hbox]
|
21
|
+
end
|
22
|
+
|
23
|
+
# define the pixel buffers
|
24
|
+
def self.define_pixbuf
|
25
|
+
pb_current = GdkPixbuf::Pixbuf.new #current pixbuf
|
26
|
+
pb_portrait = GdkPixbuf::Pixbuf.new #pixbuf in portrait format (relevant only for rotations)
|
27
|
+
|
28
|
+
return [pb_current, pb_portrait]
|
29
|
+
end
|
30
|
+
|
31
|
+
# define the necessary buttons
|
32
|
+
def self.define_buttons
|
33
|
+
#-----------Definition of Buttons ----------
|
34
|
+
|
35
|
+
#-----------Previous-Button----------------------
|
36
|
+
prev_btn = Gtk::Button.new
|
37
|
+
prev_btn.label = "Previous"
|
38
|
+
prev_btn.set_tooltip_text "Previous image"
|
39
|
+
prev_btn.sensitive = false
|
40
|
+
|
41
|
+
#-----------Next-Button-----------------------
|
42
|
+
next_btn = Gtk::Button.new
|
43
|
+
next_btn.label = "Next"
|
44
|
+
next_btn.set_tooltip_text "Next image"
|
45
|
+
next_btn.sensitive = false
|
46
|
+
|
47
|
+
#-----------Rotate-Button----------------------
|
48
|
+
rotate_btn = Gtk::Button.new
|
49
|
+
rotate_btn.label = "Rotate"
|
50
|
+
rotate_btn.set_tooltip_text "Rotate image"
|
51
|
+
rotate_btn.sensitive = false
|
52
|
+
|
53
|
+
#-----------Select-Button-----------------
|
54
|
+
select_btn = Gtk::Button.new
|
55
|
+
select_btn.label = "Move to \"Selected\""
|
56
|
+
select_btn.set_tooltip_text "Move the image to the \"Selected\" folder"
|
57
|
+
select_btn.sensitive = false
|
58
|
+
|
59
|
+
#-----------Delete-Button---------------------
|
60
|
+
delete_btn = Gtk::Button.new
|
61
|
+
delete_btn.label = "Move to \"Deleted\""
|
62
|
+
delete_btn.set_tooltip_text "Move the image to the \"Deleted\" folder"
|
63
|
+
delete_btn.sensitive = false
|
64
|
+
|
65
|
+
return {prev_btn: prev_btn, next_btn: next_btn, rotate_btn: rotate_btn, select_btn: select_btn, delete_btn: delete_btn}
|
66
|
+
end
|
67
|
+
|
68
|
+
# define the menu
|
69
|
+
def self.define_menu
|
70
|
+
mb = Gtk::MenuBar.new
|
71
|
+
file = Gtk::MenuItem.new :label => "File"
|
72
|
+
filemenu = Gtk::Menu.new
|
73
|
+
file.set_submenu filemenu
|
74
|
+
|
75
|
+
open_file = Gtk::MenuItem.new :label => "Open file"
|
76
|
+
quit = Gtk::MenuItem.new :label => "Exit"
|
77
|
+
|
78
|
+
filemenu.append open_file
|
79
|
+
filemenu.append quit
|
80
|
+
mb.append file
|
81
|
+
|
82
|
+
return [mb, open_file, quit]
|
83
|
+
end
|
84
|
+
|
85
|
+
# define the image parameters
|
86
|
+
def self.define_img_parameters(win_width, win_height, img_current)
|
87
|
+
pb_current, pb_portrait = define_pixbuf
|
88
|
+
|
89
|
+
img_parameters = {
|
90
|
+
dir_path: "", # path of the image folder
|
91
|
+
selected_path: "",
|
92
|
+
deleted_path: "",
|
93
|
+
name: "",
|
94
|
+
rotation_case: 'A',
|
95
|
+
ind: 0,
|
96
|
+
is_landscape: true,
|
97
|
+
all_orig_img: [],
|
98
|
+
all_orig_pb: [],
|
99
|
+
pb_current: pb_current,
|
100
|
+
pb_portrait: pb_portrait,
|
101
|
+
img_current: img_current, # current image
|
102
|
+
img_max_w: (win_width * 0.37).to_i, # max width for each image
|
103
|
+
img_max_h: (win_height * 0.37).to_i , # max height for each image
|
104
|
+
reduction_factor: 0.95
|
105
|
+
}
|
106
|
+
return img_parameters
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Wuffl
|
2
|
+
module ImageActions
|
3
|
+
|
4
|
+
# determine the dimensions of the given image with FastImage-gem
|
5
|
+
def self.img_dimensions_fi (filename)
|
6
|
+
img_dim = FastImage.size("#{filename}")
|
7
|
+
img_orig_width_fi = img_dim[0]
|
8
|
+
img_orig_height_fi = img_dim[1]
|
9
|
+
|
10
|
+
if img_orig_width_fi > img_orig_height_fi
|
11
|
+
is_orig_landscape = true
|
12
|
+
else
|
13
|
+
is_orig_landscape = false
|
14
|
+
end
|
15
|
+
|
16
|
+
fastimage_infos = [is_orig_landscape, img_orig_width_fi.to_i, img_orig_height_fi.to_i]
|
17
|
+
end
|
18
|
+
|
19
|
+
# resize the image if too big - for landscape mode
|
20
|
+
def self.landscape_pic(pb_orig, img_width, img_height, img_max_w, img_max_h, reduction_factor)
|
21
|
+
if img_width > img_max_w || img_height > img_max_h
|
22
|
+
while img_width > img_max_w || img_height > img_max_h do
|
23
|
+
img_width *= reduction_factor
|
24
|
+
img_height *= reduction_factor
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
img_width = img_width.to_i
|
29
|
+
img_height = img_height.to_i
|
30
|
+
pb_orig = pb_orig.scale(img_width, img_height, :bilinear)
|
31
|
+
return pb_orig
|
32
|
+
end
|
33
|
+
|
34
|
+
# resize the image if too big - for portrait mode
|
35
|
+
def self.portrait_pic(pb_orig, rotate_pic, img_width, img_height, img_max_h, reduction_factor)
|
36
|
+
if rotate_pic == true
|
37
|
+
pb_orig = pb_orig.rotate(:clockwise)
|
38
|
+
z = img_width
|
39
|
+
img_width = img_height
|
40
|
+
img_height = z
|
41
|
+
end
|
42
|
+
|
43
|
+
if img_height > img_max_h
|
44
|
+
while img_height > img_max_h do
|
45
|
+
img_width *= reduction_factor
|
46
|
+
img_height *= reduction_factor
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
img_height = img_height.to_i
|
51
|
+
img_width = img_width.to_i
|
52
|
+
|
53
|
+
pb_orig = pb_orig.scale(img_width, img_height, :bilinear)
|
54
|
+
return pb_orig
|
55
|
+
end
|
56
|
+
|
57
|
+
# prepare image for loading
|
58
|
+
def self.prepare_pixbuf(filename, array_of_orig_pixbufs, img_max_w, img_max_h, reduction_factor)
|
59
|
+
fi_infos = img_dimensions_fi(filename)
|
60
|
+
pixbuf_infos = GdkPixbuf::Pixbuf.get_file_info(filename)
|
61
|
+
is_landscape = fi_infos[0]
|
62
|
+
pixbuf_width = pixbuf_infos[1]
|
63
|
+
pixbuf_height = pixbuf_infos[2]
|
64
|
+
|
65
|
+
img_width = pixbuf_width
|
66
|
+
img_height = pixbuf_height
|
67
|
+
|
68
|
+
pb_orig = GdkPixbuf::Pixbuf.new :file => filename, :width => img_width, :height => img_height
|
69
|
+
if fi_infos[1] > fi_infos[2] #landscape format
|
70
|
+
pb_orig = landscape_pic(pb_orig, img_width, img_height, img_max_w, img_max_h, reduction_factor)
|
71
|
+
else #portrait format
|
72
|
+
if (fi_infos[1] == pixbuf_infos[1]) && (fi_infos[2] == pixbuf_infos[2])
|
73
|
+
rotate_pic = false
|
74
|
+
else
|
75
|
+
rotate_pic = true
|
76
|
+
end
|
77
|
+
pb_orig = portrait_pic(pb_orig, rotate_pic, img_width, img_height, img_max_h, reduction_factor)
|
78
|
+
end
|
79
|
+
|
80
|
+
array_of_orig_pixbufs << pb_orig
|
81
|
+
return [array_of_orig_pixbufs, is_landscape]
|
82
|
+
end
|
83
|
+
|
84
|
+
# display the current image
|
85
|
+
def self.show_img(img_curr, pb_curr)
|
86
|
+
img_curr.set_pixbuf(pb_curr)
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'gtk3'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'fastimage'
|
4
|
+
require 'stringio'
|
5
|
+
require_relative 'basic_elements'
|
6
|
+
require_relative 'actions'
|
7
|
+
|
8
|
+
module Wuffl
|
9
|
+
|
10
|
+
class WufflInterface
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
|
14
|
+
# get the screen resolution
|
15
|
+
@win_width, @win_height = Actions.get_resolution
|
16
|
+
|
17
|
+
# define the window
|
18
|
+
@window = BasicElements.define_window(@win_width*0.45, @win_height*0.45)
|
19
|
+
|
20
|
+
# define the boxes
|
21
|
+
@vbox, @hbox = BasicElements.define_boxes
|
22
|
+
|
23
|
+
# prepare the current image
|
24
|
+
@img_current = Gtk::Image.new
|
25
|
+
|
26
|
+
@halign = Gtk::Alignment.new 0.5, 0, 0, 0
|
27
|
+
|
28
|
+
# define image parameters
|
29
|
+
@img_parameters = BasicElements.define_img_parameters(@win_width, @win_height, @img_current)
|
30
|
+
|
31
|
+
# define buttons
|
32
|
+
@button_set = BasicElements.define_buttons
|
33
|
+
|
34
|
+
# define menu
|
35
|
+
@mb, @open_file, @quit = BasicElements.define_menu
|
36
|
+
|
37
|
+
# define the set for the packing of boxes
|
38
|
+
@box_set = {hbox: @hbox, vbox: @vbox, halign: @halign, mb: @mb}
|
39
|
+
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def run
|
45
|
+
|
46
|
+
# open file action
|
47
|
+
@open_file.signal_connect("activate") do |w|
|
48
|
+
@img_parameters = BasicElements.define_img_parameters(@win_width, @win_height, @img_current)
|
49
|
+
@img_parameters = Actions.open_file_action(@window, @img_parameters, @button_set)
|
50
|
+
end
|
51
|
+
|
52
|
+
# previous button action
|
53
|
+
@button_set[:prev_btn].signal_connect("clicked") do
|
54
|
+
@img_parameters = Actions.prev_next_btn_action(@window, @img_parameters, "prev")
|
55
|
+
end
|
56
|
+
|
57
|
+
# next button action
|
58
|
+
@button_set[:next_btn].signal_connect("clicked") do
|
59
|
+
@img_parameters = Actions.prev_next_btn_action(@window, @img_parameters, "next")
|
60
|
+
end
|
61
|
+
|
62
|
+
# rotate button action
|
63
|
+
#----------------Rotate-Button------------------
|
64
|
+
@button_set[:rotate_btn].signal_connect("clicked") do
|
65
|
+
@img_parameters = Actions.rotate_btn_action(@img_parameters)
|
66
|
+
end
|
67
|
+
|
68
|
+
# select button action
|
69
|
+
@button_set[:select_btn].signal_connect("clicked") do
|
70
|
+
@img_parameters = Actions.select_delete_btn_action(@window, @img_parameters, @button_set, "select")
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# delete button action
|
75
|
+
@button_set[:delete_btn].signal_connect("clicked") do
|
76
|
+
@img_parameters = Actions.select_delete_btn_action(@window, @img_parameters, @button_set, "delete")
|
77
|
+
end
|
78
|
+
|
79
|
+
@quit.signal_connect("activate") {Gtk.main_quit}
|
80
|
+
|
81
|
+
# pack the boxes
|
82
|
+
@box_set = Actions.pack_boxes(@window, @img_parameters[:img_current], @box_set, @button_set)
|
83
|
+
|
84
|
+
@window.show_all
|
85
|
+
Gtk.main
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wuffl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- BluePeony
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-05-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: gtk3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fileutils
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: fastimage
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: stringio
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: "# Wuffl\n<strong> 1. Introduction </strong> \nWuffl is a simple image
|
70
|
+
viewer which can move pictures either to a „Selected“ folder or to a „Deleted“ folder.
|
71
|
+
\"Selected\" and \"Deleted\" folders are located in the same folder as the chosen
|
72
|
+
picture. Wuffl is available for Linux Ubuntu >=14.04, Mac and Windows. \n\nImagine
|
73
|
+
you review you vacation photographs and you need to select which pictures to show
|
74
|
+
to your friends at the next party or you work your way through a bunch of old pictures
|
75
|
+
and you need to select a view of them for a anniversary celebration. Wuffl allows
|
76
|
+
you to move the picture to an extra folder (\"Selected\" folder) or to a \"Deleted\"
|
77
|
+
folder while viewing it. \n<p>\nSource for the icons of the buttons: http://icons.mysitemyway.com
|
78
|
+
\ \n</p>\n<strong> 2. Installation </strong> \nTo use the Wuffl image viewer you
|
79
|
+
require:\n<ul>\n <li> <a href=\"https://www.ruby-lang.org/en/downloads/\"><code>ruby</code></a>
|
80
|
+
(v2.4)\n</ul>\nas well as the following Ruby gems: \n<ul>\n <li><code>fastimage</code>\n
|
81
|
+
\ <li><code>fileutils</code>\n <li><code>gtk3</code>\n <li><code>stringio</code></li>\n</ul>
|
82
|
+
\ \n\nGet the Wullf image viewer by typing ```gem install wuffl``` in your command
|
83
|
+
line.\n\n<strong>3. Usage</strong>\nUsing your terminal or cmd start Wuffl by typing
|
84
|
+
\ \n<code> wuffl</code>\n\nAfter starting Wuffl you'll see a start screen with a
|
85
|
+
line of buttons at the bottom. \nTo select an image go to the top left corner,
|
86
|
+
klick on <code>File</code> → <code>Open file</code> and browse to the location of
|
87
|
+
you images. Then select an image and click <code>open</code> or just doubleclick
|
88
|
+
on the selected image. \n\nThe buttons from left to right mean: <code>show previous
|
89
|
+
image</code>, <code>rotate current image</code>, <code>move the current image to
|
90
|
+
the folder 'Selected'</code>, <code>show next image</code>, <code>move the current
|
91
|
+
image to the folder 'Deleted'</code>.\n\n<strong>4. Remarks</strong> \nIf you have
|
92
|
+
any remarks, bugs, questions etc. please tell me, I'd be happy to help. \n"
|
93
|
+
email: blue.peony2314@gmail.com
|
94
|
+
executables:
|
95
|
+
- wuffl
|
96
|
+
extensions: []
|
97
|
+
extra_rdoc_files: []
|
98
|
+
files:
|
99
|
+
- LICENSE
|
100
|
+
- README
|
101
|
+
- bin/wuffl
|
102
|
+
- lib/wuffl/actions.rb
|
103
|
+
- lib/wuffl/basic_elements.rb
|
104
|
+
- lib/wuffl/image_actions.rb
|
105
|
+
- lib/wuffl/wuffl_interface.rb
|
106
|
+
homepage: https://github.com/BluePeony/wuffl
|
107
|
+
licenses:
|
108
|
+
- MIT
|
109
|
+
metadata: {}
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options: []
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '1.9'
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubygems_version: 3.4.8
|
126
|
+
signing_key:
|
127
|
+
specification_version: 4
|
128
|
+
summary: image viewer with options to put an image either to the folder 'Selected'
|
129
|
+
or the folder 'Deleted' to preselect them for further use
|
130
|
+
test_files: []
|