themactep-grumblr 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ nbproject
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ == Grumblr
2
+
3
+ Copyright (c) 2009, Paul Philippov
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,42 @@
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
+ * Ruby Gtk2 bindings and Ruby/GTK bindings for Mozilla
19
+ from Ruby-GNOME2 Project
20
+ http://ruby-gnome2.sourceforge.jp/
21
+
22
+ * LibXml Ruby
23
+ http://libxml.rubyforge.org/
24
+
25
+ * REST Client
26
+ http://github.com/adamwiggins/rest-client/
27
+
28
+
29
+ === Installation ===
30
+
31
+ $ sudo gem source -a http://gems.github.com/
32
+ $ sudo gem install themactep-grumblr
33
+
34
+
35
+ === Homepage ===
36
+
37
+ http://github.com/themactep/grumblr
38
+
39
+
40
+ === Authors ===
41
+
42
+ Paul Philippov <themactep@gmail.com>
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gemspec|
8
+ gemspec.name = "grumblr"
9
+ gemspec.summary = "Tumblr companion for GNOME"
10
+ gemspec.description = "Grumblr is a message poster to Tumblr blogs from GNOME."
11
+ gemspec.email = "themactep@gmail.com"
12
+ gemspec.homepage = "http://github.com/themactep/grumblr"
13
+ gemspec.authors = ["Paul Philippov"]
14
+ gemspec.add_dependency "libxml-ruby"
15
+ gemspec.add_dependency "rest-client"
16
+ end
17
+ rescue LoadError
18
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
19
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.0.0
data/bin/grumblr ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # $Id: grumblr.rb 2009/08/09 21:48:53 EET paul Exp $
3
+ #
4
+ =begin
5
+ Grumblr - Tumblr companion for GNOME.
6
+
7
+ Copyright (c) 2009, Paul Philippov <paul@ppds.ws>
8
+
9
+ This software is released under the BSD License.
10
+ http://creativecommons.org/licenses/BSD/
11
+ =end
12
+
13
+ app_file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
14
+ APP_ROOT = File.expand_path(File.dirname(app_file))
15
+ Dir.glob(APP_ROOT + '/../**/').reverse.each { |d| $:.unshift d }
16
+
17
+ begin
18
+ require 'rubygems'
19
+ rescue LoadError
20
+ nil
21
+ end
22
+
23
+ require 'grumblr/core'
24
+
25
+ Grumblr::Core.new
26
+ Thread.new { Grumblr::StatusIcon.new }
27
+ $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;
data/grumblr.gemspec ADDED
@@ -0,0 +1,66 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{grumblr}
8
+ s.version = "2.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Paul Philippov"]
12
+ s.date = %q{2009-08-22}
13
+ s.default_executable = %q{grumblr}
14
+ s.description = %q{Grumblr is a message poster to Tumblr blogs from GNOME.}
15
+ s.email = %q{themactep@gmail.com}
16
+ s.executables = ["grumblr"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README"
20
+ ]
21
+ s.files = [
22
+ ".gitignore",
23
+ "LICENSE",
24
+ "README",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "bin/grumblr",
28
+ "data/grumblr.desktop",
29
+ "grumblr.gemspec",
30
+ "images/audio.bmp",
31
+ "images/chat.bmp",
32
+ "images/grumblr.svg",
33
+ "images/link.bmp",
34
+ "images/photo.bmp",
35
+ "images/quote.bmp",
36
+ "images/spinner.gif",
37
+ "images/text.bmp",
38
+ "images/video.bmp",
39
+ "lib/grumblr/core.rb",
40
+ "lib/grumblr/ui.rb",
41
+ "lib/ppds/class_factory.rb",
42
+ "lib/ppds/config.rb",
43
+ "lib/ppds/tumblr.rb"
44
+ ]
45
+ s.homepage = %q{http://github.com/themactep/grumblr}
46
+ s.rdoc_options = ["--charset=UTF-8"]
47
+ s.require_paths = ["lib"]
48
+ s.rubygems_version = %q{1.3.5}
49
+ s.summary = %q{Tumblr companion for GNOME}
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
+ s.add_runtime_dependency(%q<libxml-ruby>, [">= 0"])
57
+ s.add_runtime_dependency(%q<rest-client>, [">= 0"])
58
+ else
59
+ s.add_dependency(%q<libxml-ruby>, [">= 0"])
60
+ s.add_dependency(%q<rest-client>, [">= 0"])
61
+ end
62
+ else
63
+ s.add_dependency(%q<libxml-ruby>, [">= 0"])
64
+ s.add_dependency(%q<rest-client>, [">= 0"])
65
+ end
66
+ end
data/images/audio.bmp ADDED
Binary file
data/images/chat.bmp ADDED
Binary file
@@ -0,0 +1,110 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
6
+ xmlns:cc="http://creativecommons.org/ns#"
7
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8
+ xmlns:svg="http://www.w3.org/2000/svg"
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12
+ width="512"
13
+ height="512"
14
+ id="svg6643"
15
+ version="1.1"
16
+ inkscape:version="0.47pre1 r21720"
17
+ sodipodi:docname="grumblr.svg"
18
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
19
+ <defs
20
+ id="defs6645">
21
+ <inkscape:perspective
22
+ sodipodi:type="inkscape:persp3d"
23
+ inkscape:vp_x="0 : 32 : 1"
24
+ inkscape:vp_y="0 : 1000 : 0"
25
+ inkscape:vp_z="64 : 32 : 1"
26
+ inkscape:persp3d-origin="32 : 21.333333 : 1"
27
+ id="perspective6651" />
28
+ <inkscape:perspective
29
+ id="perspective6631"
30
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
31
+ inkscape:vp_z="1 : 0.5 : 1"
32
+ inkscape:vp_y="0 : 1000 : 0"
33
+ inkscape:vp_x="0 : 0.5 : 1"
34
+ sodipodi:type="inkscape:persp3d" />
35
+ </defs>
36
+ <sodipodi:namedview
37
+ id="base"
38
+ pagecolor="#ffffff"
39
+ bordercolor="#666666"
40
+ borderopacity="1.0"
41
+ inkscape:pageopacity="0.0"
42
+ inkscape:pageshadow="2"
43
+ inkscape:zoom="0.97227183"
44
+ inkscape:cx="255.53213"
45
+ inkscape:cy="255.7224"
46
+ inkscape:current-layer="layer1"
47
+ showgrid="false"
48
+ inkscape:document-units="px"
49
+ inkscape:grid-bbox="true"
50
+ inkscape:window-width="1440"
51
+ inkscape:window-height="825"
52
+ inkscape:window-x="0"
53
+ inkscape:window-y="25"
54
+ inkscape:window-maximized="1">
55
+ <inkscape:grid
56
+ type="xygrid"
57
+ id="grid2823"
58
+ empspacing="5"
59
+ visible="true"
60
+ enabled="true"
61
+ snapvisiblegridlinesonly="true" />
62
+ </sodipodi:namedview>
63
+ <metadata
64
+ id="metadata6648">
65
+ <rdf:RDF>
66
+ <cc:Work
67
+ rdf:about="">
68
+ <dc:format>image/svg+xml</dc:format>
69
+ <dc:type
70
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
71
+ <dc:title />
72
+ </cc:Work>
73
+ </rdf:RDF>
74
+ </metadata>
75
+ <g
76
+ id="layer1"
77
+ inkscape:label="Layer 1"
78
+ inkscape:groupmode="layer"
79
+ transform="translate(0,448)">
80
+ <g
81
+ id="g2820"
82
+ transform="matrix(0.99800965,0,0,0.99800965,0.1055055,-2.036518)">
83
+ <path
84
+ id="path3605"
85
+ 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"
86
+ style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans" />
87
+ <path
88
+ id="path8-7-6"
89
+ 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"
90
+ style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#ffa500;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
91
+ sodipodi:nodetypes="cssccssscsccscc" />
92
+ <path
93
+ id="path10-5"
94
+ style="fill:#ffffff;stroke:none"
95
+ 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" />
96
+ <path
97
+ id="path14-3"
98
+ style="fill:#ffffff;stroke:none"
99
+ 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" />
100
+ <path
101
+ id="path16-3"
102
+ style="fill:#000000"
103
+ 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" />
104
+ <path
105
+ id="path18-1"
106
+ style="fill:#000000"
107
+ 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" />
108
+ </g>
109
+ </g>
110
+ </svg>
data/images/link.bmp ADDED
Binary file
data/images/photo.bmp ADDED
Binary file
data/images/quote.bmp ADDED
Binary file
Binary file
data/images/text.bmp ADDED
Binary file
data/images/video.bmp ADDED
Binary file
@@ -0,0 +1,38 @@
1
+ require 'ppds/config'
2
+ require 'ppds/tumblr'
3
+ require 'grumblr/ui'
4
+ require 'gtk2'
5
+
6
+ module Grumblr
7
+ IMAGE_ROOT = File.expand_path(File.join(APP_ROOT, '..', 'images'))
8
+
9
+ class Core
10
+ attr_accessor :blog
11
+
12
+ def initialize
13
+ $app = self
14
+
15
+ $cfg = Ppds::Config.new 'grumblr'
16
+
17
+ $gui = Grumblr::UI.new
18
+ $gui.show_all
19
+
20
+ $api = Ppds::Tumblr.new
21
+ if $api.authenticate($cfg.get(:email), $cfg.get(:password))
22
+ $gui.add Grumblr::Dashboard.new
23
+ else
24
+ $gui.add Grumblr::SettingsFrame.new
25
+ end
26
+ end
27
+
28
+ def main
29
+ Gtk::main
30
+ end
31
+
32
+ def quit
33
+ Gtk::main_quit
34
+ ensure
35
+ $cfg.save
36
+ end
37
+ end
38
+ end
data/lib/grumblr/ui.rb ADDED
@@ -0,0 +1,598 @@
1
+ require 'gtk2'
2
+
3
+ module Grumblr
4
+ class UI < Gtk::Window
5
+
6
+ attr_accessor :logo
7
+
8
+ def initialize
9
+ super Gtk::Window::TOPLEVEL
10
+
11
+ filename = File.join(Grumblr::IMAGE_ROOT, 'grumblr.svg')
12
+ self.logo = Gdk::Pixbuf.new filename, 128, 128
13
+
14
+ self.set_size_request 480, 360
15
+ self.set_allow_shrink false
16
+ self.set_title 'Grumblr'
17
+
18
+ self.set_icon self.logo
19
+ self.set_default_width $cfg.get(:window_width).to_i
20
+ self.set_default_height $cfg.get(:window_height).to_i
21
+ self.move $cfg.get(:window_x_pos).to_i, $cfg.get(:window_y_pos).to_i
22
+ self.signal_connect(:destroy) { quit }
23
+ self.signal_connect(:delete_event) { minimize }
24
+ self.signal_connect(:check_resize) do |widget|
25
+ x, y = widget.position
26
+ w, h = widget.size
27
+ $cfg.set :window_x_pos, x
28
+ $cfg.set :window_y_pos, y
29
+ $cfg.set :window_width, w
30
+ $cfg.set :window_height, h
31
+ end
32
+ signal_connect(:window_state_event) do |widget, e|
33
+ case e.event_type
34
+ when Gdk::Event::WINDOW_STATE
35
+ minimize if e.changed_mask.iconified? and e.new_window_state.iconified?
36
+ end
37
+ end
38
+ show
39
+ end
40
+
41
+ def minimize
42
+ self.hide
43
+ end
44
+ end
45
+
46
+ class Dashboard < Gtk::VBox
47
+ def initialize
48
+ super false, 4
49
+
50
+ ##
51
+ ### Statusbar
52
+ ##
53
+ @statusbar = Gtk::Statusbar.new
54
+
55
+ ##
56
+ ### Notebook
57
+ ##
58
+ @notebook = Gtk::Notebook.new
59
+ @notebook.set_homogeneous true
60
+ @notebook.set_tab_pos Gtk::POS_LEFT
61
+
62
+ #
63
+ # Text page
64
+ #
65
+ @text_title = Gtk::Entry.new
66
+
67
+ @format = Gtk::CheckButton.new '_markdown'
68
+ @format.set_active $cfg.get(:format_markdown)
69
+ @format.signal_connect(:toggled) do |widget|
70
+ $cfg.set :format_markdown, widget.active?
71
+ end
72
+
73
+ label = Gtk::Label.new 'Body'
74
+
75
+ box = Gtk::HBox.new false, 8
76
+ box.pack_start label, false
77
+ box.pack_start @format, false
78
+
79
+ page = Gtk::VBox.new false, 4
80
+ page.set_border_width 8
81
+ page.pack_with_label 'Title (optional)', @text_title
82
+ page.pack_start box, false
83
+ page.pack_start multiline_entry(:text_body), true
84
+
85
+ @notebook.add_page_with_tab page, 'Text'
86
+
87
+ #
88
+ # Link page
89
+ #
90
+ @link_url = Gtk::Entry.new
91
+ @link_name = Gtk::Entry.new
92
+ link_description = multiline_entry :link_description
93
+
94
+ page = Gtk::VBox.new false, 4
95
+ page.set_border_width 8
96
+ page.pack_with_label 'URL', @link_url
97
+ page.pack_with_label 'Name (optional)', @link_name
98
+ page.pack_with_label'Description (optional)', link_description, true
99
+
100
+ @notebook.add_page_with_tab page, 'Link'
101
+
102
+ #
103
+ # Chat page
104
+ #
105
+ @chat_title = Gtk::Entry.new
106
+ chat_conversation = multiline_entry :chat_conversation
107
+
108
+ page = Gtk::VBox.new false, 4
109
+ page.set_border_width 8
110
+ page.pack_with_label 'Title (optional)', @chat_title
111
+ page.pack_with_label 'Conversation', chat_conversation, true
112
+
113
+ @notebook.add_page_with_tab page, 'Chat'
114
+
115
+ #
116
+ # Quote page
117
+ #
118
+ @quote_source = Gtk::Entry.new
119
+ quote_quote = multiline_entry :quote_quote
120
+
121
+ page = Gtk::VBox.new false, 4
122
+ page.set_border_width 8
123
+ page.pack_with_label 'Quote', quote_quote, true
124
+ page.pack_with_label 'Source (optional)', @quote_source
125
+
126
+ @notebook.add_page_with_tab page, 'Quote'
127
+
128
+ #
129
+ # Photo page
130
+ #
131
+ filter = Gtk::FileFilter.new
132
+ filter.set_name "Images"
133
+ filter.add_mime_type "image/*"
134
+
135
+ @photo_source = Gtk::Entry.new
136
+ @photo_click_through_url = Gtk::Entry.new
137
+ photo_data = file_chooser_button :photo_data, filter
138
+ photo_caption = multiline_entry :photo_caption
139
+
140
+ page = Gtk::VBox.new false, 4
141
+ page.set_border_width 8
142
+ page.pack_with_label 'File', photo_data
143
+ page.pack_with_label 'Source', @photo_source
144
+ page.pack_with_label 'Caption', photo_caption, true
145
+ page.pack_with_label 'Link (optional)', @photo_click_through_url
146
+
147
+ @notebook.add_page_with_tab page, 'Photo'
148
+
149
+ #
150
+ # Audio page
151
+ #
152
+ if $api.user.can_upload_audio == '1'
153
+ filter = Gtk::FileFilter.new
154
+ filter.set_name "Audio"
155
+ filter.add_mime_type "audio/*"
156
+
157
+ audio_data = file_chooser_button :audio_data, filter
158
+ audio_caption = multiline_entry :audio_caption
159
+
160
+ page = Gtk::VBox.new false, 4
161
+ page.set_border_width 8
162
+ page.pack_with_label 'File', audio_data
163
+ page.pack_with_label 'Caption (optional)', audio_caption, true
164
+
165
+ @notebook.add_page_with_tab page, 'Audio'
166
+ end
167
+
168
+ #
169
+ # Video page
170
+ #
171
+ @video_embed = Gtk::Entry.new
172
+ video_caption = multiline_entry :video_caption
173
+
174
+ page = Gtk::VBox.new false, 4
175
+ page.set_border_width 8
176
+
177
+ if $api.user.can_upload_video == '1'
178
+ filter = Gtk::FileFilter.new
179
+ filter.set_name "Video"
180
+ filter.add_mime_type "video/*"
181
+
182
+ video_data = file_chooser_button :video_data, filter
183
+ @video_title = Gtk::Entry.new
184
+
185
+ page.pack_with_label 'File', video_data
186
+ page.pack_with_label 'Title (optional)', @video_title
187
+ end
188
+
189
+ page.pack_with_label 'Embed code / YouTube link', @video_embed
190
+ page.pack_with_label 'Caption (optional)', video_caption, true
191
+
192
+ @notebook.add_page_with_tab page, 'Video'
193
+
194
+ ##
195
+ ### Toolbar
196
+ ##
197
+
198
+ toolbar = Gtk::Toolbar.new
199
+ toolbar.icon_size = Gtk::IconSize::MENU
200
+
201
+ icon = Gtk::Image.new Gtk::Stock::HOME, Gtk::IconSize::MENU
202
+ item = Gtk::ToolButton.new icon, 'Tumblelog'
203
+ item.signal_connect(:clicked) do
204
+ Thread.new { system('xdg-open "%s"' % $app.blog.url) }
205
+ end
206
+ toolbar.insert 0, item
207
+
208
+ icon = Gtk::Image.new Gtk::Stock::PREFERENCES, Gtk::IconSize::MENU
209
+ item = Gtk::ToolButton.new icon, 'Dashboard'
210
+ item.signal_connect(:clicked) do
211
+ Thread.new { system('xdg-open "http://www.tumblr.com/tumblelog/%s"' % $app.blog.name) }
212
+ end
213
+ toolbar.insert 1, item
214
+
215
+ combo = Gtk::ComboBox.new
216
+ active_blog = $cfg.get(:active_blog) || nil
217
+ active_blog_idx = nil
218
+ $api.blogs.each_with_index do |blog, idx|
219
+ combo.append_text blog.title
220
+ active_blog_idx = idx if blog.name.eql?(active_blog)
221
+ active_blog_idx = idx if active_blog_idx.nil? and blog.is_primary == "yes"
222
+ end
223
+ combo.signal_connect(:changed) do |widget|
224
+ $app.blog = $api.blogs[widget.active]
225
+ $cfg.set :active_blog, $app.blog.name
226
+ @statusbar.push 0, $app.blog.title
227
+ end
228
+ combo.set_active(active_blog_idx)
229
+ item = Gtk::ToolItem.new
230
+ item.set_expand true
231
+ item.add combo
232
+ toolbar.insert 2, item
233
+
234
+ icon = Gtk::Image.new Gtk::Stock::QUIT, Gtk::IconSize::MENU
235
+ item = Gtk::ToolButton.new icon, 'Quit'
236
+ item.set_homogeneous false
237
+ item.signal_connect(:clicked) do
238
+ $app.quit
239
+ end
240
+ toolbar.insert 3, item
241
+
242
+ ##
243
+ ### Buttons
244
+ ##
245
+ clear_button = Gtk::Button.new 'Clear'
246
+ clear_button.set_focus_on_click false
247
+ clear_button.signal_connect(:clicked) do |widget|
248
+ page = @notebook.get_nth_page @notebook.page
249
+ message_type = @notebook.get_menu_label_text page
250
+ reset_form message_type.downcase
251
+ end
252
+
253
+ @private_button = Gtk::ToggleButton.new 'Private'
254
+ @private_button.signal_connect(:toggled) do |widget|
255
+ $cfg.set :private, widget.active?
256
+ end
257
+ @private_button.set_active $cfg.get(:private)
258
+
259
+ submit_button = Gtk::Button.new 'Send'
260
+ submit_button.signal_connect(:released) do |widget|
261
+ post
262
+ end
263
+
264
+ button_box = Gtk::HBox.new false, 4
265
+ button_box.pack_start clear_button, false
266
+ button_box.pack_start @private_button, false
267
+ button_box.pack_start submit_button
268
+
269
+ ##
270
+ ### Layout
271
+ ##
272
+ pack_start toolbar, false
273
+ pack_start @notebook
274
+ pack_start button_box, false
275
+ pack_start @statusbar, false
276
+ show_all
277
+ end
278
+
279
+ def post
280
+ page = @notebook.get_nth_page @notebook.page
281
+ message_type = @notebook.get_menu_label_text(page).downcase
282
+
283
+ mandatory_data = collect_data_for Ppds::Tumblr::MANDATORY_FIELDS, message_type
284
+ concurent_data = collect_data_for Ppds::Tumblr::CONCURENT_FIELDS, message_type
285
+ optional_data = collect_data_for Ppds::Tumblr::OPTIONAL_FIELDS, message_type
286
+
287
+ mandatory_data.each do |key, value|
288
+ raise "Mandatory field %s is not set!" % key if not value or value.empty?
289
+ end unless Ppds::Tumblr::MANDATORY_FIELDS[message_type].empty?
290
+
291
+ unless Ppds::Tumblr::CONCURENT_FIELDS[message_type].empty?
292
+ concurent_data.delete_if { |x,y| y == "" or y.nil? }
293
+ raise "None of fields %s is set!" % Ppds::Tumblr::CONCURENT_FIELDS[message_type].join(", ") if concurent_data.empty?
294
+ end
295
+
296
+ optional_data.delete_if { |x,y| y == "" or y.nil? }
297
+
298
+ data = {
299
+ :generator => 'Grumblr',
300
+ :email => $cfg.get(:email),
301
+ :password => $cfg.get(:password),
302
+ :channel_id => $app.blog.name,
303
+ :group => $app.blog.name + '.tumblr.com',
304
+ :type => message_type,
305
+ :format => @format.active? ? 'markdown' : 'html',
306
+ :private => @private_button.active? ? 1 : 0
307
+ }
308
+
309
+ data.merge! mandatory_data
310
+ data.merge! concurent_data
311
+ data.merge! optional_data
312
+
313
+ data.update({:data => File.read(data['data'])}) if data.has_key?('data') and data['data'] != ""
314
+
315
+ $api.query 'write', data
316
+ MessageDialog.new "Message posted", Gtk::Stock::DIALOG_INFO
317
+ reset_form message_type
318
+ rescue Exception
319
+ MessageDialog.new $!
320
+ end
321
+
322
+ def collect_data_for(fieldset, message_type)
323
+ data = {}
324
+ for key in fieldset[message_type]
325
+ name = "@#{message_type}_#{key.gsub(/-/,'_')}"
326
+ if var = instance_variable_get(name)
327
+ value = var.get_value
328
+ data.merge!({ key => value })
329
+ end
330
+ end
331
+ data
332
+ end
333
+
334
+ def file_chooser_button(name, filter = nil)
335
+ button = Gtk::FileChooserButton.new('Open', Gtk::FileChooser::ACTION_OPEN)
336
+ if filter
337
+ button.add_filter(filter)
338
+ button.set_filter(filter)
339
+ end
340
+ button.signal_connect(:selection_changed) do |widget|
341
+ puts widget.filename
342
+ end
343
+ button.show_all
344
+ instance_variable_set "@#{name}", button
345
+ end
346
+
347
+ def multiline_entry(name)
348
+ instance_variable_set "@#{name}", Gtk::TextBuffer.new
349
+
350
+ view = Gtk::TextView.new
351
+ view.set_buffer instance_variable_get("@#{name}")
352
+ view.set_wrap_mode Gtk::TextTag::WRAP_WORD
353
+ view.set_right_margin 5
354
+ view.set_left_margin 5
355
+
356
+ window = Gtk::ScrolledWindow.new
357
+ window.set_shadow_type Gtk::SHADOW_IN
358
+ window.set_policy Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC
359
+ window.add view
360
+ window.show_all
361
+ end
362
+
363
+ def reset_fields_for(fieldset, message_type)
364
+ for key in fieldset[message_type]
365
+ name = "@#{message_type}_#{key.gsub(/-/,'_')}"
366
+ var = instance_variable_get(name)
367
+ var.clear if var
368
+ end
369
+ end
370
+
371
+ def reset_form(message_type)
372
+ [ Ppds::Tumblr::MANDATORY_FIELDS,
373
+ Ppds::Tumblr::CONCURENT_FIELDS,
374
+ Ppds::Tumblr::OPTIONAL_FIELDS ].each do |fieldset|
375
+ reset_fields_for(fieldset, message_type)
376
+ end
377
+ end
378
+ end
379
+
380
+ class MessageDialog < Gtk::Dialog
381
+ def initialize(text, stock = Gtk::Stock::DIALOG_ERROR)
382
+ super "Attention!", $gui, Gtk::Dialog::MODAL
383
+
384
+ message = Gtk::Label.new text
385
+ icon = Gtk::Image.new stock, Gtk::IconSize::DIALOG
386
+
387
+ hbox = Gtk::HBox.new false, 20
388
+ hbox.set_border_width 20
389
+ hbox.pack_start icon, false
390
+ hbox.pack_start message, true
391
+
392
+ self.add_button Gtk::Stock::OK, Gtk::Dialog::RESPONSE_NONE
393
+ self.signal_connect(:response) { self.destroy }
394
+ self.vbox.add hbox
395
+ self.show_all
396
+ self.run
397
+ end
398
+ end
399
+
400
+ class SettingsFrame < Gtk::HBox
401
+ def initialize
402
+ super false, 40
403
+ self.set_border_width 40
404
+
405
+ @label = Gtk::Label.new
406
+ @label.set_markup '<b>Fill-in Tumblr credentials</b>'
407
+
408
+ @text_e = Gtk::Entry.new
409
+ @text_e.set_text $cfg.get(:email).to_s
410
+
411
+ @text_p = Gtk::Entry.new
412
+ @text_p.set_visibility false
413
+ @text_p.set_text $cfg.get(:password).to_s
414
+
415
+ hbox = Gtk::HBox.new
416
+
417
+ button = Gtk::Button.new 'Cancel'
418
+ button.signal_connect(:released) { $app.quit }
419
+ hbox.pack_start button
420
+
421
+ button = Gtk::Button.new 'Sign in'
422
+ button.signal_connect(:released) { login }
423
+ hbox.pack_start button
424
+
425
+ header = Gtk::Label.new
426
+ header.set_alignment 0.0, 0.8
427
+ header.set_markup '<big><big><b>Grumblr 2.0</b></big></big>'
428
+
429
+ vbox = Gtk::VBox.new false, 4
430
+ vbox.pack_start header
431
+ vbox.pack_with_label '_Email', @text_e
432
+ vbox.pack_with_label '_Password', @text_p
433
+ vbox.pack_start @label
434
+ vbox.pack_start hbox, false
435
+
436
+ logo = Gtk::Image.new $gui.logo
437
+
438
+ self.pack_start logo
439
+ self.pack_start vbox
440
+ self.show_all
441
+ end
442
+
443
+ def login
444
+ email = @text_e.text.strip
445
+ password = @text_p.text.strip
446
+ if $api.authenticate(email, password)
447
+ $cfg.set :email, email
448
+ $cfg.set :password, password
449
+ self.destroy
450
+ @dashboard = Dashboard.new
451
+ $gui.add @dashboard
452
+ else
453
+ raise "Authentication failed"
454
+ end
455
+ rescue Exception
456
+ MessageDialog.new $!
457
+ end
458
+ end
459
+
460
+
461
+ class AboutDialog < Gtk::AboutDialog
462
+ def initialize
463
+ Gtk::AboutDialog.set_email_hook do |dialog, email|
464
+ system("xdg-email #{email}")
465
+ end
466
+ Gtk::AboutDialog.set_url_hook do |dialog, url|
467
+ system("xdg-open #{url}")
468
+ end
469
+ super
470
+ self.logo = $gui.logo
471
+ self.program_name = 'Grumblr'
472
+ self.version = '2.0'
473
+ self.copyright = "Copyright (c)2009, Paul Philippov"
474
+ self.comments = "Tumblr companion for GNOME"
475
+ self.license = "New BSD License.\nhttp://creativecommons.org/licenses/BSD/"
476
+ self.website = "http://grumblr.googlecode.com/"
477
+ self.website_label = "grumblr.googlecode.com"
478
+ self.authors = ['Paul Philippov <paul@ppds.ws>']
479
+ self.run
480
+ self.destroy
481
+ end
482
+ end
483
+
484
+ class StatusIcon < Gtk::StatusIcon
485
+ def initialize
486
+ super
487
+ self.file = File.join(Grumblr::IMAGE_ROOT, 'grumblr.svg')
488
+ self.tooltip = "Application Name Goes Here"
489
+ self.signal_connect(:activate) do
490
+ if $gui.visible?
491
+ $gui.minimize
492
+ else
493
+ $gui.move $cfg.get(:window_x_pos), $cfg.get(:window_y_pos)
494
+ $gui.show.present
495
+ end
496
+ end
497
+ self.signal_connect(:popup_menu) do |icon, button, time|
498
+ menu.popup nil, nil, button, time
499
+ end
500
+ end
501
+
502
+ def menu
503
+ menu = Gtk::Menu.new
504
+ for item in [ ontop, sep, destroy_account, sep, about, sep, quit ]
505
+ menu.append item
506
+ end
507
+ menu.show_all
508
+ end
509
+
510
+ def sep
511
+ Gtk::SeparatorMenuItem.new
512
+ end
513
+
514
+ ##
515
+ ## Destroy Config
516
+ ##
517
+ def destroy_account
518
+ icon = Gtk::ImageMenuItem.new 'Destroy account'
519
+ icon.set_image Gtk::Image.new(Gtk::Stock::STOP, Gtk::IconSize::MENU)
520
+ icon.signal_connect(:activate) do
521
+ $cfg.destroy
522
+ end
523
+ icon.show
524
+ end
525
+
526
+ def about
527
+ icon = Gtk::ImageMenuItem.new Gtk::Stock::ABOUT
528
+ icon.signal_connect(:activate) do
529
+ AboutDialog.new
530
+ end
531
+ icon.show
532
+ end
533
+
534
+ def ontop
535
+ icon = Gtk::CheckMenuItem.new 'Always on top'
536
+ icon.signal_connect(:toggled) do |widget|
537
+ $gui.keep_above = widget.active?
538
+ end
539
+ icon.show
540
+ end
541
+
542
+ def quit
543
+ icon = Gtk::ImageMenuItem.new Gtk::Stock::QUIT
544
+ icon.signal_connect(:activate) do
545
+ $app.quit
546
+ end
547
+ icon.show
548
+ end
549
+ end
550
+ end
551
+
552
+ class Gtk::Box
553
+ def pack_with_label(text, widget, expand = false)
554
+ label = Gtk::Label.new text, true
555
+ label.set_alignment 0.0, 0.5
556
+ label.set_mnemonic_widget widget
557
+ self.pack_start label, false
558
+ self.pack_start widget, expand
559
+ end
560
+ end
561
+
562
+ class Gtk::Entry
563
+ alias :get_value :text
564
+ def clear
565
+ self.set_text ''
566
+ end
567
+ end
568
+
569
+ module Gtk::FileChooser
570
+ alias :get_value :filename
571
+ alias :clear :unselect_all
572
+ end
573
+
574
+ class Gtk::Notebook
575
+ def add_page_with_tab(page, text)
576
+ filename = File.join(Grumblr::IMAGE_ROOT, '%s.bmp' % text.downcase)
577
+ icon = Gtk::Image.new filename
578
+ icon.set_padding 2, 4
579
+
580
+ label = Gtk::Label.new '_' + text, true
581
+ label.set_alignment 0.0, 0.5
582
+ label.set_padding 4, 2
583
+
584
+ box = Gtk::HBox.new false, 4
585
+ box.pack_start icon, false
586
+ box.pack_start label, true
587
+ box.show_all
588
+
589
+ self.append_page_menu page, box, label
590
+ end
591
+ end
592
+
593
+ class Gtk::TextBuffer
594
+ alias :get_value :get_text
595
+ def clear
596
+ self.set_text ''
597
+ end
598
+ end
@@ -0,0 +1,17 @@
1
+ module Ppds
2
+ class ClassFactory
3
+ def initialize(data=[])
4
+ for att in data
5
+ set(att.name.gsub(/-/,'_'), att.value)
6
+ end
7
+ end
8
+
9
+ def get(name)
10
+ instance_variable_get("@#{name}")
11
+ end
12
+
13
+ def set(name, value)
14
+ instance_variable_set("@#{name}", value)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,47 @@
1
+ require 'gconf2'
2
+
3
+ module Ppds
4
+ class Config
5
+ @@gconf = GConf::Client.default
6
+
7
+ def gconf
8
+ @@gconf
9
+ end
10
+
11
+ def initialize(app)
12
+ @root = '/apps/%s' % app
13
+ gconf.add_dir(@root)
14
+ end
15
+
16
+ def name_from_key(key)
17
+ key.split('/').last
18
+ end
19
+
20
+ def key_from_name(name)
21
+ [ @root, name ].join("/")
22
+ end
23
+
24
+ def all
25
+ gconf.all_entries(@root)
26
+ end
27
+
28
+ def get(name)
29
+ gconf[key_from_name(name)]
30
+ end
31
+
32
+ def set(name, value)
33
+ gconf[key_from_name(name)] = value
34
+ end
35
+
36
+ def save
37
+ gconf.suggest_sync
38
+ end
39
+
40
+ def destroy
41
+ for one in all
42
+ gconf.unset(one.key)
43
+ end
44
+ save
45
+ end
46
+ end
47
+ 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,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: themactep-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-22 00:00:00 -07:00
13
+ default_executable: grumblr
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: libxml-ruby
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: rest-client
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
+ description: Grumblr is a message poster to Tumblr blogs from GNOME.
36
+ email: themactep@gmail.com
37
+ executables:
38
+ - grumblr
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README
44
+ files:
45
+ - .gitignore
46
+ - LICENSE
47
+ - README
48
+ - Rakefile
49
+ - VERSION
50
+ - bin/grumblr
51
+ - data/grumblr.desktop
52
+ - grumblr.gemspec
53
+ - images/audio.bmp
54
+ - images/chat.bmp
55
+ - images/grumblr.svg
56
+ - images/link.bmp
57
+ - images/photo.bmp
58
+ - images/quote.bmp
59
+ - images/spinner.gif
60
+ - images/text.bmp
61
+ - images/video.bmp
62
+ - lib/grumblr/core.rb
63
+ - lib/grumblr/ui.rb
64
+ - lib/ppds/class_factory.rb
65
+ - lib/ppds/config.rb
66
+ - lib/ppds/tumblr.rb
67
+ has_rdoc: false
68
+ homepage: http://github.com/themactep/grumblr
69
+ licenses:
70
+ post_install_message:
71
+ rdoc_options:
72
+ - --charset=UTF-8
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: "0"
86
+ version:
87
+ requirements: []
88
+
89
+ rubyforge_project:
90
+ rubygems_version: 1.3.5
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Tumblr companion for GNOME
94
+ test_files: []
95
+