grumblr 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ == Grumblr
2
+
3
+ Copyright (c) 2009, Paul Philippov <themactep@gmail.com>
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10
+ * Neither the name of the Paul Philippov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13
+
data/README ADDED
@@ -0,0 +1,37 @@
1
+ == Grumblr ==
2
+
3
+ Grumblr is a tool to post messages to your Tumblr blog from GNOME.
4
+
5
+ === Licence ===
6
+
7
+ New BSD License
8
+
9
+
10
+ === Requirements: ===
11
+
12
+ * Ruby
13
+ http://www.ruby-lang.org/
14
+
15
+ * GTK+
16
+ http://www.mozilla.org/
17
+
18
+ * LibXml Ruby
19
+ http://libxml.rubyforge.org/
20
+
21
+ * REST Client
22
+ http://github.com/adamwiggins/rest-client/
23
+
24
+
25
+ === Installation ===
26
+
27
+ $ sudo gem install grumblr
28
+
29
+
30
+ === Homepage ===
31
+
32
+ http://themactep.com/grumblr/
33
+
34
+
35
+ === Authors ===
36
+
37
+ Paul Philippov <themactep@gmail.com>
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+ require 'rake/testtask'
7
+ require 'spec/rake/spectask'
8
+
9
+ spec = Gem::Specification.new do |s|
10
+ s.name = 'grumblr'
11
+ s.version = '2.0.0'
12
+ s.has_rdoc = false
13
+ s.extra_rdoc_files = ['README', 'LICENSE']
14
+ s.summary = "Tumblr companion for GNOME"
15
+ s.description = "Grumblr is a message poster to Tumblr blogs from GNOME"
16
+ s.author = 'Paul Philippov'
17
+ s.email = 'themactep@gmail.com'
18
+ s.homepage = 'http://themactep.com/grumblr/'
19
+ s.rubyforge_project = 'grumblr'
20
+ s.executables = ['grumblr']
21
+ s.files = %w(LICENSE README Rakefile) + Dir.glob("{bin,data,lib,spec}/**/*")
22
+ s.require_path = "lib"
23
+ s.bindir = "bin"
24
+ s.add_dependency "ppds-libs"
25
+ s.add_dependency "libxml-ruby"
26
+ s.add_dependency "rest-client"
27
+ end
28
+
29
+ Rake::GemPackageTask.new(spec) do |p|
30
+ p.gem_spec = spec
31
+ p.need_tar = true
32
+ p.need_zip = true
33
+ end
34
+
35
+ Rake::RDocTask.new do |rdoc|
36
+ files =['README', 'LICENSE', 'lib/**/*.rb']
37
+ rdoc.rdoc_files.add(files)
38
+ rdoc.main = "README" # page to start on
39
+ rdoc.title = "grumblr Docs"
40
+ rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
41
+ rdoc.options << '--line-numbers'
42
+ end
43
+
44
+ Rake::TestTask.new do |t|
45
+ t.test_files = FileList['test/**/*.rb']
46
+ end
47
+
48
+ Spec::Rake::SpecTask.new do |t|
49
+ t.spec_files = FileList['spec/**/*.rb']
50
+ end
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ =begin
4
+ Grumblr - Tumblr companion for GNOME.
5
+
6
+ Copyright (c) 2009, Paul Philippov <themactep@gmail.com>
7
+
8
+ This software is released under the BSD License.
9
+ http://creativecommons.org/licenses/BSD/
10
+ =end
11
+
12
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+
14
+ begin
15
+ require 'rubygems'
16
+ rescue LoadError
17
+ nil
18
+ end
19
+
20
+ require 'grumblr/core'
21
+
22
+ Grumblr::Core.new
23
+ Thread.new { Grumblr::StatusIcon.new }
24
+ $app.main
@@ -0,0 +1,9 @@
1
+ [Desktop Entry]
2
+ Name=Grumblr
3
+ Comment=Tumblr companion for GNOME
4
+ Exec=grumblr
5
+ Icon=grumblr
6
+ StartupNotify=true
7
+ Terminal=false
8
+ Type=Application
9
+ Categories=Network;
Binary file
Binary file
@@ -0,0 +1,19 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="512" height="512">
3
+ <g transform="translate(0,448)">
4
+ <g transform="matrix(0.99800965,0,0,0.99800965,0.1055055,-2.036518)">
5
+ <path d="m 256.54485,-440.84091 c -138.2071,0 -250.6386,112.43141 -250.6386,250.6386 0,138.207112 112.4315,250.35856 250.6386,250.35856 138.2071,0 250.35856,-112.151448 250.35856,-250.35856 0,-138.20719 -112.15146,-250.6386 -250.35856,-250.6386 z"
6
+ style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
7
+ <path d="m 256.53616,-410.78108 c -121.94187,0 -220.57008,98.62809 -220.57008,220.57003 0,121.941854 98.62821,220.30747 220.57008,220.30747 86.47283,0 154.49575,-36.9921661 190.59612,-109.260948 -58.46055,-11.134087 -137.96685,-4.693712 -227.88298,14.376441 -2.13629,0.856345 -4.64884,0.716097 -6.67656,-0.372685 -2.02771,-1.088783 -5.0478,-4.419462 -5.0478,-14.003756 0,-9.584293 1.16361,-10.670758 2.59282,-12.474768 1.42921,-1.804009 3.67982,-2.929738 5.98054,-2.991394 46.4443,-9.85029 91.86095,-16.03896 133.39239,-18.11825 41.5314,-2.0793 79.20574,-0.24608 110.28502,6.03941 0.17498,-0.006 0.35015,-0.006 0.52514,0 10.52606,-25.756 16.54273,-53.89965 16.54273,-83.50152 0,-121.94194 -98.3656,-220.57003 -220.30746,-220.57003 l 4e-5,0 z"
8
+ style="color:#000000;fill:#ffa500;fill-opacity:1;fill-rule:nonzero;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
9
+ <path d="m 363.80317,-207.45687 c 0,33.53488 -24.97949,60.7203 -55.79328,60.7203 -30.81378,0 -55.79331,-27.18542 -55.79331,-60.7203 0,-33.5349 24.97953,-60.72031 55.79331,-60.72031 30.81379,0 55.79328,27.18541 55.79328,60.72031 z"
10
+ style="fill:#ffffff" />
11
+ <path d="m 467.85013,-212.31538 c 0,29.39149 -20.48895,53.21798 -45.76332,53.21798 -25.27436,0 -45.76332,-23.82649 -45.76332,-53.21798 0,-29.39149 20.48896,-53.21804 45.76332,-53.21804 25.27437,0 45.76332,23.82655 45.76332,53.21804 z"
12
+ style="fill:#ffffff"/>
13
+ <path d="m 316.30525,-183.79454 c 0,9.97261 -8.32793,18.05695 -18.60095,18.05695 -10.273,0 -18.60092,-8.08434 -18.60092,-18.05695 0,-9.97252 8.32792,-18.05686 18.60092,-18.05686 10.27302,0 18.60095,8.08434 18.60095,18.05686 z"
14
+ style="fill:#000000" />
15
+ <path d="m 421.37793,-193.1727 c 0,8.1655 -7.05731,14.78492 -15.76289,14.78492 -8.70565,0 -15.76292,-6.61942 -15.76292,-14.78492 0,-8.1655 7.05727,-14.78489 15.76292,-14.78489 8.70558,0 15.76289,6.61939 15.76289,14.78489 z"
16
+ style="fill:#000000" />
17
+ </g>
18
+ </g>
19
+ </svg>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,37 @@
1
+ require 'ppds/config'
2
+ require 'ppds/tumblr'
3
+ require 'grumblr/ui'
4
+ require 'gtk2'
5
+
6
+ module Grumblr
7
+
8
+ class Core
9
+ attr_accessor :blog
10
+
11
+ def initialize
12
+ $app = self
13
+
14
+ $cfg = Ppds::Config.new 'grumblr'
15
+
16
+ $gui = Grumblr::UI.new
17
+ $gui.show_all
18
+
19
+ $api = Ppds::Tumblr.new
20
+ if $api.authenticate($cfg.get(:email), $cfg.get(:password))
21
+ $gui.add Grumblr::Dashboard.new
22
+ else
23
+ $gui.add Grumblr::SettingsFrame.new
24
+ end
25
+ end
26
+
27
+ def main
28
+ Gtk::main
29
+ end
30
+
31
+ def quit
32
+ Gtk::main_quit
33
+ ensure
34
+ $cfg.save
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,600 @@
1
+ require 'gtk2'
2
+
3
+ module Grumblr
4
+
5
+ DATA_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data'))
6
+
7
+ class UI < Gtk::Window
8
+
9
+ attr_accessor :logo
10
+
11
+ def initialize
12
+ super Gtk::Window::TOPLEVEL
13
+
14
+ filename = File.join(Grumblr::DATA_ROOT, 'pixmaps', 'grumblr.svg')
15
+ self.logo = Gdk::Pixbuf.new filename, 128, 128
16
+
17
+ self.set_size_request 480, 360
18
+ self.set_allow_shrink false
19
+ self.set_title 'Grumblr'
20
+
21
+ self.set_icon self.logo
22
+ self.set_default_width $cfg.get(:window_width).to_i
23
+ self.set_default_height $cfg.get(:window_height).to_i
24
+ self.move $cfg.get(:window_x_pos).to_i, $cfg.get(:window_y_pos).to_i
25
+ self.signal_connect(:destroy) { quit }
26
+ self.signal_connect(:delete_event) { minimize }
27
+ self.signal_connect(:check_resize) do |widget|
28
+ x, y = widget.position
29
+ w, h = widget.size
30
+ $cfg.set :window_x_pos, x
31
+ $cfg.set :window_y_pos, y
32
+ $cfg.set :window_width, w
33
+ $cfg.set :window_height, h
34
+ end
35
+ signal_connect(:window_state_event) do |widget, e|
36
+ case e.event_type
37
+ when Gdk::Event::WINDOW_STATE
38
+ minimize if e.changed_mask.iconified? and e.new_window_state.iconified?
39
+ end
40
+ end
41
+ show
42
+ end
43
+
44
+ def minimize
45
+ self.hide
46
+ end
47
+ end
48
+
49
+ class Dashboard < Gtk::VBox
50
+ def initialize
51
+ super false, 4
52
+
53
+ ##
54
+ ### Statusbar
55
+ ##
56
+ @statusbar = Gtk::Statusbar.new
57
+
58
+ ##
59
+ ### Notebook
60
+ ##
61
+ @notebook = Gtk::Notebook.new
62
+ @notebook.set_homogeneous true
63
+ @notebook.set_tab_pos Gtk::POS_LEFT
64
+
65
+ #
66
+ # Text page
67
+ #
68
+ @text_title = Gtk::Entry.new
69
+
70
+ @format = Gtk::CheckButton.new '_markdown'
71
+ @format.set_active $cfg.get(:format_markdown)
72
+ @format.signal_connect(:toggled) do |widget|
73
+ $cfg.set :format_markdown, widget.active?
74
+ end
75
+
76
+ label = Gtk::Label.new 'Body'
77
+
78
+ box = Gtk::HBox.new false, 8
79
+ box.pack_start label, false
80
+ box.pack_start @format, false
81
+
82
+ page = Gtk::VBox.new false, 4
83
+ page.set_border_width 8
84
+ page.pack_with_label 'Title (optional)', @text_title
85
+ page.pack_start box, false
86
+ page.pack_start multiline_entry(:text_body), true
87
+
88
+ @notebook.add_page_with_tab page, 'Text'
89
+
90
+ #
91
+ # Link page
92
+ #
93
+ @link_url = Gtk::Entry.new
94
+ @link_name = Gtk::Entry.new
95
+ link_description = multiline_entry :link_description
96
+
97
+ page = Gtk::VBox.new false, 4
98
+ page.set_border_width 8
99
+ page.pack_with_label 'URL', @link_url
100
+ page.pack_with_label 'Name (optional)', @link_name
101
+ page.pack_with_label'Description (optional)', link_description, true
102
+
103
+ @notebook.add_page_with_tab page, 'Link'
104
+
105
+ #
106
+ # Chat page
107
+ #
108
+ @chat_title = Gtk::Entry.new
109
+ chat_conversation = multiline_entry :chat_conversation
110
+
111
+ page = Gtk::VBox.new false, 4
112
+ page.set_border_width 8
113
+ page.pack_with_label 'Title (optional)', @chat_title
114
+ page.pack_with_label 'Conversation', chat_conversation, true
115
+
116
+ @notebook.add_page_with_tab page, 'Chat'
117
+
118
+ #
119
+ # Quote page
120
+ #
121
+ @quote_source = Gtk::Entry.new
122
+ quote_quote = multiline_entry :quote_quote
123
+
124
+ page = Gtk::VBox.new false, 4
125
+ page.set_border_width 8
126
+ page.pack_with_label 'Quote', quote_quote, true
127
+ page.pack_with_label 'Source (optional)', @quote_source
128
+
129
+ @notebook.add_page_with_tab page, 'Quote'
130
+
131
+ #
132
+ # Photo page
133
+ #
134
+ filter = Gtk::FileFilter.new
135
+ filter.set_name "Images"
136
+ filter.add_mime_type "image/*"
137
+
138
+ @photo_source = Gtk::Entry.new
139
+ @photo_click_through_url = Gtk::Entry.new
140
+ photo_data = file_chooser_button :photo_data, filter
141
+ photo_caption = multiline_entry :photo_caption
142
+
143
+ page = Gtk::VBox.new false, 4
144
+ page.set_border_width 8
145
+ page.pack_with_label 'File', photo_data
146
+ page.pack_with_label 'Source', @photo_source
147
+ page.pack_with_label 'Caption', photo_caption, true
148
+ page.pack_with_label 'Link (optional)', @photo_click_through_url
149
+
150
+ @notebook.add_page_with_tab page, 'Photo'
151
+
152
+ #
153
+ # Audio page
154
+ #
155
+ if $api.user.can_upload_audio == '1'
156
+ filter = Gtk::FileFilter.new
157
+ filter.set_name "Audio"
158
+ filter.add_mime_type "audio/*"
159
+
160
+ audio_data = file_chooser_button :audio_data, filter
161
+ audio_caption = multiline_entry :audio_caption
162
+
163
+ page = Gtk::VBox.new false, 4
164
+ page.set_border_width 8
165
+ page.pack_with_label 'File', audio_data
166
+ page.pack_with_label 'Caption (optional)', audio_caption, true
167
+
168
+ @notebook.add_page_with_tab page, 'Audio'
169
+ end
170
+
171
+ #
172
+ # Video page
173
+ #
174
+ @video_embed = Gtk::Entry.new
175
+ video_caption = multiline_entry :video_caption
176
+
177
+ page = Gtk::VBox.new false, 4
178
+ page.set_border_width 8
179
+
180
+ if $api.user.can_upload_video == '1'
181
+ filter = Gtk::FileFilter.new
182
+ filter.set_name "Video"
183
+ filter.add_mime_type "video/*"
184
+
185
+ video_data = file_chooser_button :video_data, filter
186
+ @video_title = Gtk::Entry.new
187
+
188
+ page.pack_with_label 'File', video_data
189
+ page.pack_with_label 'Title (optional)', @video_title
190
+ end
191
+
192
+ page.pack_with_label 'Embed code / YouTube link', @video_embed
193
+ page.pack_with_label 'Caption (optional)', video_caption, true
194
+
195
+ @notebook.add_page_with_tab page, 'Video'
196
+
197
+ ##
198
+ ### Toolbar
199
+ ##
200
+
201
+ toolbar = Gtk::Toolbar.new
202
+ toolbar.icon_size = Gtk::IconSize::MENU
203
+
204
+ icon = Gtk::Image.new Gtk::Stock::HOME, Gtk::IconSize::MENU
205
+ item = Gtk::ToolButton.new icon, 'Tumblelog'
206
+ item.signal_connect(:clicked) do
207
+ Thread.new { system('xdg-open "%s"' % $app.blog.url) }
208
+ end
209
+ toolbar.insert 0, item
210
+
211
+ icon = Gtk::Image.new Gtk::Stock::PREFERENCES, Gtk::IconSize::MENU
212
+ item = Gtk::ToolButton.new icon, 'Dashboard'
213
+ item.signal_connect(:clicked) do
214
+ Thread.new { system('xdg-open "http://www.tumblr.com/tumblelog/%s"' % $app.blog.name) }
215
+ end
216
+ toolbar.insert 1, item
217
+
218
+ combo = Gtk::ComboBox.new
219
+ active_blog = $cfg.get(:active_blog) || nil
220
+ active_blog_idx = nil
221
+ $api.blogs.each_with_index do |blog, idx|
222
+ combo.append_text blog.title
223
+ active_blog_idx = idx if blog.name.eql?(active_blog)
224
+ active_blog_idx = idx if active_blog_idx.nil? and blog.is_primary == "yes"
225
+ end
226
+ combo.signal_connect(:changed) do |widget|
227
+ $app.blog = $api.blogs[widget.active]
228
+ $cfg.set :active_blog, $app.blog.name
229
+ @statusbar.push 0, $app.blog.title
230
+ end
231
+ combo.set_active(active_blog_idx)
232
+ item = Gtk::ToolItem.new
233
+ item.set_expand true
234
+ item.add combo
235
+ toolbar.insert 2, item
236
+
237
+ icon = Gtk::Image.new Gtk::Stock::QUIT, Gtk::IconSize::MENU
238
+ item = Gtk::ToolButton.new icon, 'Quit'
239
+ item.set_homogeneous false
240
+ item.signal_connect(:clicked) do
241
+ $app.quit
242
+ end
243
+ toolbar.insert 3, item
244
+
245
+ ##
246
+ ### Buttons
247
+ ##
248
+ clear_button = Gtk::Button.new 'Clear'
249
+ clear_button.set_focus_on_click false
250
+ clear_button.signal_connect(:clicked) do |widget|
251
+ page = @notebook.get_nth_page @notebook.page
252
+ message_type = @notebook.get_menu_label_text page
253
+ reset_form message_type.downcase
254
+ end
255
+
256
+ @private_button = Gtk::ToggleButton.new 'Private'
257
+ @private_button.signal_connect(:toggled) do |widget|
258
+ $cfg.set :private, widget.active?
259
+ end
260
+ @private_button.set_active $cfg.get(:private)
261
+
262
+ submit_button = Gtk::Button.new 'Send'
263
+ submit_button.signal_connect(:released) do |widget|
264
+ post
265
+ end
266
+
267
+ button_box = Gtk::HBox.new false, 4
268
+ button_box.pack_start clear_button, false
269
+ button_box.pack_start @private_button, false
270
+ button_box.pack_start submit_button
271
+
272
+ ##
273
+ ### Layout
274
+ ##
275
+ pack_start toolbar, false
276
+ pack_start @notebook
277
+ pack_start button_box, false
278
+ pack_start @statusbar, false
279
+ show_all
280
+ end
281
+
282
+ def post
283
+ page = @notebook.get_nth_page @notebook.page
284
+ message_type = @notebook.get_menu_label_text(page).downcase
285
+
286
+ mandatory_data = collect_data_for Ppds::Tumblr::MANDATORY_FIELDS, message_type
287
+ concurent_data = collect_data_for Ppds::Tumblr::CONCURENT_FIELDS, message_type
288
+ optional_data = collect_data_for Ppds::Tumblr::OPTIONAL_FIELDS, message_type
289
+
290
+ mandatory_data.each do |key, value|
291
+ raise "Mandatory field %s is not set!" % key if not value or value.empty?
292
+ end unless Ppds::Tumblr::MANDATORY_FIELDS[message_type].empty?
293
+
294
+ unless Ppds::Tumblr::CONCURENT_FIELDS[message_type].empty?
295
+ concurent_data.delete_if { |x,y| y == "" or y.nil? }
296
+ raise "None of fields %s is set!" % Ppds::Tumblr::CONCURENT_FIELDS[message_type].join(", ") if concurent_data.empty?
297
+ end
298
+
299
+ optional_data.delete_if { |x,y| y == "" or y.nil? }
300
+
301
+ data = {
302
+ :generator => 'Grumblr',
303
+ :email => $cfg.get(:email),
304
+ :password => $cfg.get(:password),
305
+ :channel_id => $app.blog.name,
306
+ :group => $app.blog.name + '.tumblr.com',
307
+ :type => message_type,
308
+ :format => @format.active? ? 'markdown' : 'html',
309
+ :private => @private_button.active? ? 1 : 0
310
+ }
311
+
312
+ data.merge! mandatory_data
313
+ data.merge! concurent_data
314
+ data.merge! optional_data
315
+
316
+ data.update({:data => File.read(data['data'])}) if data.has_key?('data') and data['data'] != ""
317
+
318
+ $api.query 'write', data
319
+ MessageDialog.new "Message posted", Gtk::Stock::DIALOG_INFO
320
+ reset_form message_type
321
+ rescue Exception
322
+ MessageDialog.new $!
323
+ end
324
+
325
+ def collect_data_for(fieldset, message_type)
326
+ data = {}
327
+ for key in fieldset[message_type]
328
+ name = "@#{message_type}_#{key.gsub(/-/,'_')}"
329
+ if var = instance_variable_get(name)
330
+ value = var.get_value
331
+ data.merge!({ key => value })
332
+ end
333
+ end
334
+ data
335
+ end
336
+
337
+ def file_chooser_button(name, filter = nil)
338
+ button = Gtk::FileChooserButton.new('Open', Gtk::FileChooser::ACTION_OPEN)
339
+ if filter
340
+ button.add_filter(filter)
341
+ button.set_filter(filter)
342
+ end
343
+ button.signal_connect(:selection_changed) do |widget|
344
+ puts widget.filename
345
+ end
346
+ button.show_all
347
+ instance_variable_set "@#{name}", button
348
+ end
349
+
350
+ def multiline_entry(name)
351
+ instance_variable_set "@#{name}", Gtk::TextBuffer.new
352
+
353
+ view = Gtk::TextView.new
354
+ view.set_buffer instance_variable_get("@#{name}")
355
+ view.set_wrap_mode Gtk::TextTag::WRAP_WORD
356
+ view.set_right_margin 5
357
+ view.set_left_margin 5
358
+
359
+ window = Gtk::ScrolledWindow.new
360
+ window.set_shadow_type Gtk::SHADOW_IN
361
+ window.set_policy Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC
362
+ window.add view
363
+ window.show_all
364
+ end
365
+
366
+ def reset_fields_for(fieldset, message_type)
367
+ for key in fieldset[message_type]
368
+ name = "@#{message_type}_#{key.gsub(/-/,'_')}"
369
+ var = instance_variable_get(name)
370
+ var.clear if var
371
+ end
372
+ end
373
+
374
+ def reset_form(message_type)
375
+ [ Ppds::Tumblr::MANDATORY_FIELDS,
376
+ Ppds::Tumblr::CONCURENT_FIELDS,
377
+ Ppds::Tumblr::OPTIONAL_FIELDS ].each do |fieldset|
378
+ reset_fields_for(fieldset, message_type)
379
+ end
380
+ end
381
+ end
382
+
383
+ class MessageDialog < Gtk::Dialog
384
+ def initialize(text, stock = Gtk::Stock::DIALOG_ERROR)
385
+ super "Attention!", $gui, Gtk::Dialog::MODAL
386
+
387
+ message = Gtk::Label.new text
388
+ icon = Gtk::Image.new stock, Gtk::IconSize::DIALOG
389
+
390
+ hbox = Gtk::HBox.new false, 20
391
+ hbox.set_border_width 20
392
+ hbox.pack_start icon, false
393
+ hbox.pack_start message, true
394
+
395
+ self.add_button Gtk::Stock::OK, Gtk::Dialog::RESPONSE_NONE
396
+ self.signal_connect(:response) { self.destroy }
397
+ self.vbox.add hbox
398
+ self.show_all
399
+ self.run
400
+ end
401
+ end
402
+
403
+ class SettingsFrame < Gtk::HBox
404
+ def initialize
405
+ super false, 40
406
+ self.set_border_width 40
407
+
408
+ @label = Gtk::Label.new
409
+ @label.set_markup '<b>Fill-in Tumblr credentials</b>'
410
+
411
+ @text_e = Gtk::Entry.new
412
+ @text_e.set_text $cfg.get(:email).to_s
413
+
414
+ @text_p = Gtk::Entry.new
415
+ @text_p.set_visibility false
416
+ @text_p.set_text $cfg.get(:password).to_s
417
+
418
+ hbox = Gtk::HBox.new
419
+
420
+ button = Gtk::Button.new 'Cancel'
421
+ button.signal_connect(:released) { $app.quit }
422
+ hbox.pack_start button
423
+
424
+ button = Gtk::Button.new 'Sign in'
425
+ button.signal_connect(:released) { login }
426
+ hbox.pack_start button
427
+
428
+ header = Gtk::Label.new
429
+ header.set_alignment 0.0, 0.8
430
+ header.set_markup '<big><big><b>Grumblr 2.0</b></big></big>'
431
+
432
+ vbox = Gtk::VBox.new false, 4
433
+ vbox.pack_start header
434
+ vbox.pack_with_label '_Email', @text_e
435
+ vbox.pack_with_label '_Password', @text_p
436
+ vbox.pack_start @label
437
+ vbox.pack_start hbox, false
438
+
439
+ logo = Gtk::Image.new $gui.logo
440
+
441
+ self.pack_start logo
442
+ self.pack_start vbox
443
+ self.show_all
444
+ end
445
+
446
+ def login
447
+ email = @text_e.text.strip
448
+ password = @text_p.text.strip
449
+ if $api.authenticate(email, password)
450
+ $cfg.set :email, email
451
+ $cfg.set :password, password
452
+ self.destroy
453
+ @dashboard = Dashboard.new
454
+ $gui.add @dashboard
455
+ else
456
+ raise "Authentication failed"
457
+ end
458
+ rescue Exception
459
+ MessageDialog.new $!
460
+ end
461
+ end
462
+
463
+
464
+ class AboutDialog < Gtk::AboutDialog
465
+ def initialize
466
+ Gtk::AboutDialog.set_email_hook do |dialog, email|
467
+ system("xdg-email #{email}")
468
+ end
469
+ Gtk::AboutDialog.set_url_hook do |dialog, url|
470
+ system("xdg-open #{url}")
471
+ end
472
+ super
473
+ self.logo = $gui.logo
474
+ self.program_name = 'Grumblr'
475
+ self.version = '2.0.0'
476
+ self.copyright = "Copyright (c)2009, Paul Philippov"
477
+ self.comments = "Tumblr companion for GNOME"
478
+ self.license = "New BSD License.\nhttp://creativecommons.org/licenses/BSD/"
479
+ self.website = "http://themactep.com/grumblr/"
480
+ self.authors = ['Paul Philippov <themactep@gmail.com>']
481
+ self.run
482
+ self.destroy
483
+ end
484
+ end
485
+
486
+ class StatusIcon < Gtk::StatusIcon
487
+ def initialize
488
+ super
489
+ self.file = File.join(Grumblr::DATA_ROOT, 'pixmaps', 'grumblr.svg')
490
+ self.tooltip = "Application Name Goes Here"
491
+ self.signal_connect(:activate) do
492
+ if $gui.visible?
493
+ $gui.minimize
494
+ else
495
+ $gui.move $cfg.get(:window_x_pos), $cfg.get(:window_y_pos)
496
+ $gui.show.present
497
+ end
498
+ end
499
+ self.signal_connect(:popup_menu) do |icon, button, time|
500
+ menu.popup nil, nil, button, time
501
+ end
502
+ end
503
+
504
+ def menu
505
+ menu = Gtk::Menu.new
506
+ for item in [ ontop, sep, destroy_account, sep, about, sep, quit ]
507
+ menu.append item
508
+ end
509
+ menu.show_all
510
+ end
511
+
512
+ def sep
513
+ Gtk::SeparatorMenuItem.new
514
+ end
515
+
516
+ ##
517
+ ## Destroy Config
518
+ ##
519
+ def destroy_account
520
+ icon = Gtk::ImageMenuItem.new 'Destroy account'
521
+ icon.set_image Gtk::Image.new(Gtk::Stock::STOP, Gtk::IconSize::MENU)
522
+ icon.signal_connect(:activate) do
523
+ $cfg.destroy
524
+ end
525
+ icon.show
526
+ end
527
+
528
+ def about
529
+ icon = Gtk::ImageMenuItem.new Gtk::Stock::ABOUT
530
+ icon.signal_connect(:activate) do
531
+ AboutDialog.new
532
+ end
533
+ icon.show
534
+ end
535
+
536
+ def ontop
537
+ icon = Gtk::CheckMenuItem.new 'Always on top'
538
+ icon.signal_connect(:toggled) do |widget|
539
+ $gui.keep_above = widget.active?
540
+ end
541
+ icon.show
542
+ end
543
+
544
+ def quit
545
+ icon = Gtk::ImageMenuItem.new Gtk::Stock::QUIT
546
+ icon.signal_connect(:activate) do
547
+ $app.quit
548
+ end
549
+ icon.show
550
+ end
551
+ end
552
+ end
553
+
554
+ class Gtk::Box
555
+ def pack_with_label(text, widget, expand = false)
556
+ label = Gtk::Label.new text, true
557
+ label.set_alignment 0.0, 0.5
558
+ label.set_mnemonic_widget widget
559
+ self.pack_start label, false
560
+ self.pack_start widget, expand
561
+ end
562
+ end
563
+
564
+ class Gtk::Entry
565
+ alias :get_value :text
566
+ def clear
567
+ self.set_text ''
568
+ end
569
+ end
570
+
571
+ module Gtk::FileChooser
572
+ alias :get_value :filename
573
+ alias :clear :unselect_all
574
+ end
575
+
576
+ class Gtk::Notebook
577
+ def add_page_with_tab(page, text)
578
+ filename = File.join(Grumblr::DATA_ROOT, 'pixmaps', '%s.bmp' % text.downcase)
579
+ icon = Gtk::Image.new filename
580
+ icon.set_padding 2, 4
581
+
582
+ label = Gtk::Label.new '_' + text, true
583
+ label.set_alignment 0.0, 0.5
584
+ label.set_padding 4, 2
585
+
586
+ box = Gtk::HBox.new false, 4
587
+ box.pack_start icon, false
588
+ box.pack_start label, true
589
+ box.show_all
590
+
591
+ self.append_page_menu page, box, label
592
+ end
593
+ end
594
+
595
+ class Gtk::TextBuffer
596
+ alias :get_value :get_text
597
+ def clear
598
+ self.set_text ''
599
+ end
600
+ end
@@ -0,0 +1,85 @@
1
+ require 'ppds/class_factory'
2
+ require 'rest_client'
3
+ require 'xml'
4
+
5
+ module Ppds
6
+ class User < ClassFactory
7
+ attr_accessor :default_post_format, :can_upload_audio, :can_upload_aiff,
8
+ :can_upload_video, :vimeo_login_url, :max_video_bytes_uploaded
9
+ end
10
+
11
+ class Blog < ClassFactory
12
+ attr_accessor :type, :title, :name, :url, :avatar_url,
13
+ :is_primary, :private_id
14
+ end
15
+
16
+ class Tumblr
17
+ attr_accessor :user, :blogs
18
+
19
+ MANDATORY_FIELDS = {
20
+ 'text' => ['body'],
21
+ 'link' => ['url'],
22
+ 'chat' => ['conversation'],
23
+ 'quote' => ['quote'],
24
+ 'photo' => [],
25
+ 'video' => [],
26
+ 'audio' => ['data']
27
+ }
28
+
29
+ CONCURENT_FIELDS = {
30
+ 'text' => [],
31
+ 'link' => [],
32
+ 'chat' => [],
33
+ 'quote' => [],
34
+ 'photo' => ['source', 'data'],
35
+ 'video' => ['embed', 'data'],
36
+ 'audio' => []
37
+ }
38
+
39
+ OPTIONAL_FIELDS = {
40
+ 'text' => ['title'],
41
+ 'link' => ['name', 'description'],
42
+ 'chat' => ['title'],
43
+ 'quote' => ['source'],
44
+ 'photo' => ['caption','click-through-url'],
45
+ 'video' => ['caption'], # 'title'
46
+ 'audio' => ['caption']
47
+ }
48
+
49
+ API_URL = 'http://www.tumblr.com/api/'
50
+
51
+ def initialize
52
+ self.user = nil
53
+ self.blogs = []
54
+ end
55
+
56
+ def query(action, data)
57
+ RestClient.post(API_URL + action, data)
58
+ rescue RestClient::RequestFailed
59
+ raise 'Query failed: %s' % $!
60
+ rescue RestClient::RequestTimeout
61
+ raise 'Timeout occured'
62
+ rescue Exception
63
+ raise $!
64
+ end
65
+
66
+ def authenticate(email, password)
67
+ data = {
68
+ :email => email,
69
+ :password => password
70
+ }
71
+ xml = query('authenticate', data)
72
+ @xml = XML::Parser.string(xml).parse
73
+
74
+ self.user = User.new(@xml.find_first('//tumblr/user').attributes) if @xml
75
+ self.blogs = @xml.find('//tumblr/tumblelog').map do |node|
76
+ Blog.new(node.attributes)
77
+ end
78
+
79
+ true
80
+
81
+ rescue Exception
82
+ false
83
+ end
84
+ end
85
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grumblr
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Philippov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-24 00:00:00 +07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ppds-libs
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: libxml-ruby
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: rest-client
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: Grumblr is a message poster to Tumblr blogs from GNOME
46
+ email: themactep@gmail.com
47
+ executables:
48
+ - grumblr
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README
53
+ - LICENSE
54
+ files:
55
+ - LICENSE
56
+ - README
57
+ - Rakefile
58
+ - bin/grumblr
59
+ - data/grumblr.desktop
60
+ - data/pixmaps/quote.bmp
61
+ - data/pixmaps/link.bmp
62
+ - data/pixmaps/text.bmp
63
+ - data/pixmaps/spinner.gif
64
+ - data/pixmaps/audio.bmp
65
+ - data/pixmaps/video.bmp
66
+ - data/pixmaps/photo.bmp
67
+ - data/pixmaps/grumblr.svg
68
+ - data/pixmaps/chat.bmp
69
+ - lib/ppds/tumblr.rb
70
+ - lib/grumblr/core.rb
71
+ - lib/grumblr/ui.rb
72
+ has_rdoc: true
73
+ homepage: http://themactep.com/grumblr/
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options: []
78
+
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: "0"
86
+ version:
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: "0"
92
+ version:
93
+ requirements: []
94
+
95
+ rubyforge_project: grumblr
96
+ rubygems_version: 1.3.5
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Tumblr companion for GNOME
100
+ test_files: []
101
+